init: 项目首次提交

This commit is contained in:
zwz 2026-06-01 15:25:23 +08:00
commit aecf09dbc0
337 changed files with 44818 additions and 0 deletions

3
.eslintignore Normal file
View File

@ -0,0 +1,3 @@
unpackage
node_modules
uview-ui

27
.gitignore vendored Normal file
View File

@ -0,0 +1,27 @@
.DS_Store
node_modules/
dist/
unpackage/
.hbuilderx/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
**/*.log
tests/**/coverage/
tests/e2e/reports
selenium-debug.log
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.local
.env.development
package-lock.json
yarn.lock

77
App.vue Normal file
View File

@ -0,0 +1,77 @@
<script>
import { calcDate } from '@/utils/date.js'
import { tokenTime } from '@/common/setting.js'
export default {
data() {
return { refreshLock: false }
},
onLaunch: function() {
this.refreshToken()
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
},
methods: {
// token
refreshToken() {
this.refreshTime = setInterval(() => {
const token = uni.getStorageSync('token')
const date = calcDate(token.datetime, new Date().getTime())
if (!date) return
if (date.seconds >= tokenTime && !this.refreshLock) {
this.refreshLock = true
this.$u.api
.refreshToken(token.content, '000000')
.then(res => {
this.$u.func
.refreshToken(res)
.then(() => {
this.refreshLock = false
})
.catch(() => {
this.refreshLock = false
})
})
.catch(() => {
this.refreshLock = false
})
}
}, 30000)
}
}
}
</script>
<style lang="scss">
/*uview全局样式*/
@import 'uview-ui/index.scss';
/*app全局样式*/
@import 'static/style/app.scss';
// checkbox
checkbox.curdCheck .wx-checkbox-input,
checkbox.curdCheck .uni-checkbox-input {
border-radius: 50% !important;
border-color: #c8c9cc !important;
color: #5f88ff !important;
}
checkbox.curdCheck .wx-checkbox-input-checked,
checkbox.curdCheck .uni-checkbox-input-checked {
// background-color: #5f88ff;
color: #5f88ff !important;
border-color: #5f88ff !important;
}
.uni-picker-container,
.wx-picker-container {
z-index: 10074;
}
uni-modal {
z-index: 19999 !important;
}
</style>

34
LICENSE Normal file
View File

@ -0,0 +1,34 @@
BladeX商业授权许可协议
一、 知识产权:
BladeX系列产品知识产权归上海布雷德科技有限公司所有
二、 许可:
1. 在您完全接受并遵守本协议的基础上本协议授予您使用BladeX的某些权利和非独占性许可。
2. 本协议中,将本产品使用用途分为“专业版用途”和“企业版用途”。
3. “专业版用途”定义:指个人在非团体机构中出于任何目的使用本产品(任何目的包括商业目的或非盈利目的)。
4. “企业版用途”定义:指团体机构(例如公司企业、政府、学校、军队、医院、社会团体等各类组织)(不包含集团,若集团使用则需为各个子公司分别购买企业授权)出于任何目的使用本产品(任何目的包括商业目的或非盈利目的)。
三、 约束和限制:
1. 本产品只能由您为本协议许可的目的而使用,您不得透露给任何第三方;
2. 从本产品取得的任何信息、软件、产品或服务,您不得对其进行修改、改编或基于以上内容创建同种类别的衍生产品并售卖。
3. 您不得对本产品以及与之关联的商业授权进行发布、出租、销售、分销、抵押、转让、许可或发放子许可证。
4. 本产品商业授权版可能包含一些独立功能或特性,这些功能只有在您购买商业授权后才可以使用。在未取得商业授权的情况下,您不得使用、尝试使用或复制这些授权版独立功能。
5. 若您的客户要求以源码方式交付软件,需缴纳企业版授权费用,否则本产品部分不得提供源码。
四、 不得用于非法或禁止的用途:
您在使用本产品或服务时,不得将本产品产品或服务用于任何非法用途或本协议条款、条件和声明禁止的用途。
五、 免责说明:
1. 本产品按“现状”授予许可您须自行承担使用本产品的风险。BladeX团队不对此提供任何明示、暗示或任何其它形式的担保和表示。在任何情况下对于因使用或无法使用本软件而导致的任何损失包括但不仅限于商业利润损失、业务中断或业务信息丢失BladeX团队无需向您或任何第三方负责即使BladeX团队已被告知可能会造成此类损失。在任何情况下 BladeX团队均不就任何直接的、间接的、附带的、后果性的、特别的、惩戒性的和处罚性的损害赔偿承担任何责任无论该主张是基于保证、合同、侵权包括疏忽或是基于其他原因作出。
2. 本产品可能内置有第三方服务,您应自行评估使用这些第三方服务的风险,由使用此类第三方服务而产生的纠纷,全部责任由您自行承担。
3. BladeX团队不对使用本产品构建的网站中任何信息内容以及导致的任何版权纠纷、法律争议和后果承担任何责任全部责任由您自行承担。
4. BladeX团队可能会经常提供产品更新或升级但BladeX团队没有为根据本协议许可的产品提供维护或更新的责任。
5. BladeX团队可能会按照官方制定的答疑规则为您进行答疑但BladeX团队没有为根据本协议许可的产品提供技术支持的义务或责任。
六、 权利和所有权的保留:
BladeX团队保留所有未在本协议中明确授予您的所有权利。BladeX团队保留随时更新本协议的权利并只需公示于对应产品项目的LICENSE文件无需征得您的事先同意且无需另行通知更新后的内容应于公示即时生效。您可以随时访问产品地址并查阅最新版许可条款在更新生效后您继续使用本产品则被视作您已接受了新的条款。
七、 协议终止
1. 您一旦开始复制、下载、安装或者使用本产品,即被视为完全理解并接受本协议的各项条款,在享有上述条款授予的许可权力同时,也受到相关的约束和限制,本协议许可范围以外的行为,将直接违反本协议并构成侵权。
2. 一旦您违反本协议的条款BladeX团队随时可能终止本协议、收回许可和授权并要求您承担相应法律和经济责任。

43
README.md Normal file
View File

@ -0,0 +1,43 @@
## 版权声明
* BladeX是一个商业化软件系列产品知识产权归**上海布雷德网络科技**独立所有
* 您一旦开始复制、下载、安装或者使用本产品,即被视为完全理解并接受本协议的各项条款
* 更多详情请看:[BladeX商业授权许可协议](/LICENSE)
## 答疑流程
>1. 遇到问题或Bug
>2. 业务型问题打断点调试尝试找出问题所在
>3. 系统型问题通过百度、谷歌、社区查找解决方案
>4. 未解决问题则进入技术社区进行发帖提问:[https://sns.bladex.vip/](https://sns.bladex.vip/)
>5. 将帖子地址发至商业群,特别简单三言两语就能描述清楚的也可在答疑时间内发至商业群提问
>6. 发帖的时候一定要描述清楚,详细描述遇到问题的**重现步骤**、**报错详细信息**、**相关代码与逻辑**、**使用软件版本**以及**操作系统版本**,否则随意发帖提问将会提高我们的答疑难度。
## 答疑时间
* 工作日9:00 ~ 17:00 提供答疑,周末、节假日休息,暂停答疑
* 请勿**私聊提问**,以免被其他用户的消息覆盖从而无法获得答疑
* 答疑时间外遇到问题可以将问题发帖至[技术社区](https://sns.bladex.vip/),我们后续会逐个回复
## 授权范围
* 专业版:只可用于**个人学习**及**个人私活**项目,不可用于公司或团队,不可泄露给任何第三方
* 企业版:可用于**企业名下**的任何项目,企业版员工在**未购买**专业版授权前,只授权开发**所在授权企业名下**的项目,**不得将BladeX用于个人私活**
* 共同遵守若甲方需要您提供项目源码则需代为甲方购买BladeX企业授权甲方购买后续的所有项目都无需再次购买授权
## 商用权益
* ✔️ 遵守[商业协议](/LICENSE)的前提下将BladeX系列产品用于授权范围内的商用项目并上线运营
* ✔️ 遵守[商业协议](/LICENSE)的前提下,不限制项目数,不限制服务器数
* ✔️ 遵守[商业协议](/LICENSE)的前提下,将自行编写的业务代码申请软件著作权
## 何为侵权
* ❌ 不遵守商业协议,私自销售商业源码
* ❌ 以任何理由将BladeX源码用于申请软件著作权
* ❌ 将商业源码以任何途径任何理由泄露给未授权的单位或个人
* ❌ 开发完毕项目没有为甲方购买企业授权向甲方提供了BladeX代码
* ❌ 基于BladeX拓展研发与BladeX有竞争关系的衍生框架并将其开源或销售
## 侵权后果
* 情节较轻:第一次发现警告处理
* 情节较重:封禁账号,踢出商业群,并保留追究法律责任的权利
* 情节严重:与本地律师事务所合作,以公司名义起诉侵犯计算机软件著作权
## 举报有奖
* 向官方提供有用线索并成功捣毁盗版个人或窝点,将会看成果给予 50010000 不等的现金奖励
* 官方唯一指定QQ1272154962

14
api/demo.js Normal file
View File

@ -0,0 +1,14 @@
import http from '@/http/api.js'
// 获取验证码
const captcha = (data) => {
return http.request({
url: '/api/blade-auth/oauth/captcha',
method: 'GET',
data
})
}
export default {
captcha,
}

17
api/login.js Normal file
View File

@ -0,0 +1,17 @@
import http from '@/http/api.js'
/**
* 登录
* @param {*} params
* @returns
*/
export const login = params => {
return http.request({
url: '/api/blade-auth/oauth/token',
method: 'POST',
data: params,
header: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
};

147
api/mock/home.js Normal file
View File

@ -0,0 +1,147 @@
// 获取部门数据
export function fakePosition() {
return new Promise((resolute, reject) => {
try {
const detail = {
position: "研发部",
positionArr: ['研发部', '产品部']
}
resolute(detail);
} catch (e) {
//模拟接口请求失败
reject(e);
}
})
}
// 获取公告数据
export function fakeBannerList() {
return new Promise((resolute, reject) => {
try {
const list = [{
id: '1',
img: '/static/images/home/banner1.png',
url: 'https://bladex.vip'
}, {
id: '2',
img: '/static/images/home/banner2.png',
url: 'https://bladex.vip'
}, {
id: '3',
img: '/static/images/home/banner3.png',
url: 'https://sns.bladex.vip'
}];
resolute(list);
} catch (e) {
//模拟接口请求失败
reject(e);
}
})
}
// 获取公告数据
export function fakeNoticeList() {
return new Promise((resolute, reject) => {
try {
const list = [{
id: '1',
img: '/static/images/home/focus.png',
title: '天舟五号创造多个航天新纪录'
}, {
id: '2',
img: '/static/images/home/service.png',
title: '全国累计报告接种新冠疫苗超9亿剂次'
}];
resolute(list);
} catch (e) {
//模拟接口请求失败
reject(e);
}
})
}
// 获取顶部按钮数据
export function fakeNavButton() {
return new Promise((resolute, reject) => {
try {
const list = [{
name: '公积金',
img: '/static/images/home/n1.png',
url: ''
},
{
name: '社保',
img: '/static/images/home/n2.png',
url: ''
},
{
name: '医保',
img: '/static/images/home/n3.png',
url: ''
},
{
name: '企业',
img: '/static/images/home/n4.png',
url: ''
},
{
name: '教育',
img: '/static/images/home/n5.png',
url: ''
}
];
resolute(list);
} catch (e) {
//模拟接口请求失败
reject(e);
}
})
}
// 获取服务按钮数据
export function fakeServiceButton() {
return new Promise((resolute, reject) => {
try {
const list = [{
name: '工作流',
img: '/static/images/home/s1.png',
url: '/pages/plugin/workflow/pages/workbench/index'
},
{
name: '工商年报',
img: '/static/images/home/s2.png',
url: ''
},
{
name: '税务登记',
img: '/static/images/home/s3.png',
url: ''
},
{
name: '健康码',
img: '/static/images/home/s4.png',
url: ''
},
{
name: '便民服务',
img: '/static/images/home/s5.png',
url: ''
},
{
name: '通办大厅',
img: '/static/images/home/s6.png',
url: ''
},
{
name: '智能问答',
img: '/static/images/home/s7.png',
url: ''
}
];
resolute(list);
} catch (e) {
//模拟接口请求失败
reject(e);
}
})
}

152
api/mock/service.js Normal file
View File

@ -0,0 +1,152 @@
// 获取服务数据
export function fakeSubscribeList() {
return new Promise((resolute, reject) => {
try {
const list = [{
img: '/static/images/service/s1.png',
name: '违章查询',
url: ''
},
{
img: '/static/images/service/s2.png',
name: '限行信息',
url: ''
},
{
img: '/static/images/service/s3.png',
name: '油价查询',
url: ''
},
{
img: '/static/images/service/s4.png',
name: '消费记录',
url: ''
},
{
img: '/static/images/service/s5.png',
name: '保养记录',
url: ''
},
{
img: '/static/images/service/s6.png',
name: '油耗记录',
url: ''
},
{
img: '/static/images/service/s7.png',
name: '周边查询',
url: ''
},
{
img: '/static/images/service/s8.png',
name: '教育咨询',
url: ''
}
];
resolute(list);
} catch (e) {
//模拟接口请求失败
reject(e);
}
})
}
export function fakeServiceList() {
return new Promise((resolute, reject) => {
try {
const list = [{
name: '交通出行',
list: [{
img: '/static/images/service/t1.png',
name: '地铁',
url: ''
},
{
img: '/static/images/service/t2.png',
name: '公交',
url: ''
},
{
img: '/static/images/service/t3.png',
name: '自驾',
url: ''
},
{
img: '/static/images/service/t4.png',
name: '飞机',
url: ''
},
{
img: '/static/images/service/t5.png',
name: '动车',
url: ''
},
{
img: '/static/images/service/t6.png',
name: '火车',
url: ''
},
{
img: '/static/images/service/t7.png',
name: '摩托车',
url: ''
},
{
img: '/static/images/service/t8.png',
name: '自行车',
url: ''
}
]
},
{
name: '社会保险',
list: [{
img: '/static/images/service/b1.png',
name: '人身保险',
url: ''
},
{
img: '/static/images/service/b2.png',
name: '财产保险',
url: ''
},
{
img: '/static/images/service/b3.png',
name: '火灾保险',
url: ''
},
{
img: '/static/images/service/b4.png',
name: '运输保险',
url: ''
},
{
img: '/static/images/service/b5.png',
name: '工程保险',
url: ''
},
{
img: '/static/images/service/b6.png',
name: '盗窃保险',
url: ''
},
{
img: '/static/images/service/b7.png',
name: '农业保险',
url: ''
},
{
img: '/static/images/service/b8.png',
name: '信用保险',
url: ''
}
]
},
]
resolute(list);
} catch (e) {
//模拟接口请求失败
reject(e);
}
})
}

36
api/notice.js Normal file
View File

@ -0,0 +1,36 @@
import http from '@/http/api.js'
// 获取通知公告列表
export const getList = (params) => {
return http.request({
url: '/api/blade-desk/notice/list',
method: 'GET',
params
})
}
export const remove = (ids) => {
return http.request({
url: '/api/blade-desk/notice/remove',
method: 'post',
params: {
ids,
}
})
}
export const add = (row) => {
return http.request({
url: '/api/blade-desk/notice/submit',
method: 'post',
data: row
})
}
export const update = (row) => {
return http.request({
url: '/api/blade-desk/notice/submit',
method: 'post',
data: row
})
}

151
api/reserve.js Normal file
View File

@ -0,0 +1,151 @@
import http from '@/http/api.js'
/**
* 获取部门列表
* @param {*} params
* @returns
*/
export const deptList = params => {
return http.request({
url: '/api/blade-system/dept/list-page',
method: 'GET',
params: {
...params
},
});
};
/**
* 获取事项列表
* @param {*} params
* @returns
*/
export const itemList = params => {
return http.request({
url: '/api/blade-biz/item/list',
method: 'GET',
params: {
...params
},
});
};
/**
* 预约须知
* @param {*} params
* @returns
*/
export const noticeData = params => {
return http.request({
url: '/api/blade-noticeinfo/noticeInfo/list',
method: 'GET',
params: {
...params
},
});
};
/**
* 获取可用日历
* @param {*} params
* @returns
*/
export const availableDate = params => {
return http.request({
url: '/api/blade-bookingdate/bookingDate/available-week-dates',
method: 'GET',
params: {
...params
},
});
};
/**
* 查询用户信息
* @param {*} params
* @returns
*/
export const getUserInfo = params => {
return http.request({
url: '/api/blade-system/user/info',
method: 'GET',
params: {
...params
},
});
};
/**
* 提交预约
* @param {*} params
* @returns
*/
export const submit = params => {
return http.request({
url: '/api/blade-biz/booking/submit-booking',
method: 'POST',
data: {
...params
},
});
};
/**
* 查询我的预约
* @param {*} params
* @returns
*/
export const myReserve = params => {
return http.request({
url: '/api/blade-biz/booking/list',
method: 'GET',
params: {
...params
},
});
};
/**
* 取消预约
* @param {*} params
* @returns
*/
export const cancel = params => {
return http.request({
url: '/api/blade-biz/booking/cancel',
method: 'POST',
params: {
...params
},
});
};
/**
* 获取详情
* @param {*} params
* @returns
*/
export const getDetail = params => {
return http.request({
url: '/api/blade-biz/booking/detail',
method: 'GET',
params: {
...params
},
});
};
/**
* 获取是否无感取号成功
* @param {*} params
* @returns
*/
export const getIsSuccessTicket = params => {
return http.request({
url: '/api/blade-onlineticket/onlineTicket/getByYyNo',
method: 'GET',
params: {
...params
},
});
};

43
api/system/dept.js Normal file
View File

@ -0,0 +1,43 @@
import http from '@/http/api.js'
// 部门管理
export const getList = (params) => {
return http.request({
url: '/api/blade-system/dept/lazy-list',
method: 'GET',
params
})
}
export const deptTree = (params) => {
return http.request({
url: '/api/blade-system/dept/tree',
method: 'GET',
params
})
}
export const remove = (ids) => {
return http.request({
url: '/api/blade-system/dept/remove',
method: 'post',
params: {
ids,
}
})
}
export const add = (row) => {
return http.request({
url: '/api/blade-system/dept/submit',
method: 'post',
data: row
})
}
export const update = (row) => {
return http.request({
url: '/api/blade-system/dept/submit',
method: 'post',
data: row
})
}

61
api/system/dict.js Normal file
View File

@ -0,0 +1,61 @@
import http from '@/http/api.js'
//
export const dictionary = (params) => {
return http.request({
url: '/api/blade-system/dict/dictionary',
method: 'GET',
params
})
}
export const parentList = (params) => {
return http.request({
url: '/api/blade-system/dict/parent-list',
method: 'GET',
params
})
}
export const remove = (ids) => {
return http.request({
url: '/api/blade-system/dict/remove',
method: 'post',
params: {
ids,
}
})
}
export const add = (row) => {
return http.request({
url: '/api/blade-system/dict/submit',
method: 'post',
data: row
})
}
export const update = (row) => {
return http.request({
url: '/api/blade-system/dict/submit',
method: 'post',
data: row
})
}
export const dictTree = (params) => {
return http.request({
url: '/api/blade-system/dict/tree?code=DICT',
method: 'GET',
params
})
}
export const child = (params) => {
return http.request({
url: '/api/blade-system/dict/child-list',
method: 'GET',
params
})
}

36
api/system/post.js Normal file
View File

@ -0,0 +1,36 @@
import http from '@/http/api.js'
// 部门管理
export const getList = (params) => {
return http.request({
url: '/api/blade-system/post/list',
method: 'GET',
params
})
}
export const remove = (ids) => {
return http.request({
url: '/api/blade-system/post/remove',
method: 'post',
params: {
ids,
}
})
}
export const add = (row) => {
return http.request({
url: '/api/blade-system/post/submit',
method: 'post',
data: row
})
}
export const update = (row) => {
return http.request({
url: '/api/blade-system/post/submit',
method: 'post',
data: row
})
}

69
api/system/role.js Normal file
View File

@ -0,0 +1,69 @@
import http from '@/http/api.js'
// 角色管理
export const getList = (params) => {
return http.request({
url: '/api/blade-system/role/list',
method: 'GET',
params
})
}
export const remove = (ids) => {
return http.request({
url: '/api/blade-system/role/remove',
method: 'post',
params: {
ids,
}
})
}
export const add = (row) => {
return http.request({
url: '/api/blade-system/role/submit',
method: 'post',
data: row
})
}
export const update = (row) => {
return http.request({
url: '/api/blade-system/role/submit',
method: 'post',
data: row
})
}
export const roleTree = (params) => {
return http.request({
url: '/api/blade-system/role/tree-by-id',
method: 'GET',
params
})
}
export const menuTree = (params) => {
return http.request({
url: '/api/blade-system/menu/grant-tree',
method: 'GET',
params
})
}
export const menuTreeKey = (params) => {
return http.request({
url: '/api/blade-system/menu/role-tree-keys',
method: 'GET',
params
})
}
export const grant = (data) => {
return http.request({
url: '/api/blade-system/role/grant',
method: 'post',
data,
})
}

10
api/system/tenant.js Normal file
View File

@ -0,0 +1,10 @@
import http from '@/http/api.js'
// 租户
export const tenantSelect= (params) => {
return http.request({
url: '/api/blade-system/tenant/select',
method: 'GET',
params
})
}

61
api/user.js Normal file
View File

@ -0,0 +1,61 @@
import http from '@/http/api.js'
// 获取token
const token = (tenantId, username, password, type) => {
return http.request({
url: '/api/blade-auth/oauth/token',
method: 'POST',
header: {
'Tenant-Id': tenantId
},
params: {
tenantId,
username,
mmword: password,
grant_type: "password",
scope: "all",
type
}
})
}
const refreshToken = (refresh_token, tenantId) => {
return http.request({
url: '/api/blade-auth/oauth/token',
method: 'post',
headers: {
'Tenant-Id': tenantId
},
params: {
tenantId,
refresh_token,
grant_type: "refresh_token",
scope: "all",
}
})
}
// 获取用户信息
const userInfo = () => {
return http.request({
url: '/api/blade-system/user/info',
method: 'GET',
})
}
// 获取用户信息
export const getUser = (id) => {
return http.request({
url: '/api/blade-system/user/detail',
method: 'GET',
params: {
id
}
})
}
export default {
token,
userInfo,
refreshToken
}

36
common/setting.js Normal file
View File

@ -0,0 +1,36 @@
/**
* 全局变量配置
*/
module.exports = {
// 应用名
name: 'Rider',
// 应用logo支持本地路径和网络路径
logo: '/static/images/logo.png',
// 版本号
version: '2.1.0',
// 开发环境接口Url
// #ifdef H5
devUrl: '',
// #endif
// #ifndef H5
devUrl: 'http://127.0.0.1',
// #endif
// 线上环境接口Url
prodUrl: '/housingAssuranceBookingMobile',
// 后端数据的接收方式application/json;charset=UTF-8或者application/x-www-form-urlencoded;charset=UTF-8
contentType: 'application/json;charset=UTF-8',
// 后端返回状态码
codeName: 'code',
// 操作正常code
successCode: 200,
// 登录失效code
invalidCode: 401,
// 客户端ID
clientId: 'rider',
// 客户端密钥
clientSecret: 'rider_secret',
// token过期时间
tokenTime: 3000,
// 国密sm2公钥使用后端工程 @org.springblade.test.Sm2KeyGenerator 获取
publicKey: '049bb83747f21a4f6ecfd4984e02b611f6bf8180b49818edb7349632416aa65a2e267ae14fcac6f82054888774262244424ab629357a7d98cdd23f4c4eee9f2269'
}

View File

@ -0,0 +1,77 @@
<template>
<view class="nav-wrap">
<view class="nav-title">
<image class="logo" src="/static/images/logo.png" mode="widthFix"></image>
<view class="nav-info">
<view class="nav-title__text">
{{title}}
</view>
</view>
</view>
<view class="nav-desc">
{{desc}}
</view>
</view>
</template>
<script>
export default {
props: {
desc: String,
title: String,
},
}
</script>
<style lang="scss" scoped>
.nav-wrap {
margin-top: 15px;
padding: 15px;
position: relative;
}
.lang {
position: absolute;
top: 15px;
right: 15px;
}
.nav-title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
}
.nav-info {
margin-left: 15px;
}
.nav-title__text {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
color: $u-main-color;
font-size: 20px;
font-weight: bold;
}
.logo {
width: 100px;
/* #ifndef APP-NVUE */
height: auto;
/* #endif */
}
.nav-slogan {
color: $u-tips-color;
font-size: 14px;
}
.nav-desc {
margin-top: 10px;
font-size: 14px;
color: $u-content-color;
}
</style>

View File

@ -0,0 +1,153 @@
<template>
<picker
mode="multiSelector"
:value="multiIndex"
:range="multiArray"
@change="handleValueChange"
@columnchange="handleColumnChange"
>
<slot></slot>
</picker>
</template>
<script>
const CHINA_REGIONS = require('./regions.json');
export default {
props: {
defaultRegions: {
type: Array,
default() {
return [];
}
},
defaultRegionCode: {
type: String
},
defaultRegion: [String, Array],
value: [String, Array]
},
data() {
return {
cityArr: CHINA_REGIONS[0].childs,
districtArr: CHINA_REGIONS[0].childs[0].childs,
multiIndex: [0, 0, 0],
isInitMultiArray: true
};
},
watch: {
defaultRegion: {
handler(region, oldRegion) {
if (Array.isArray(region)) {
//
oldRegion = oldRegion || [];
if (region.join('') !== oldRegion.join('')) {
this.handleDefaultRegion(region);
}
} else if (region && region.length == 6) {
this.handleDefaultRegion(region);
} else {
console.warn('defaultRegion非有效格式');
}
},
immediate: true
}
},
computed: {
multiArray() {
return this.pickedArr.map(arr => arr.map(item => item.name));
},
pickedArr() {
//
if (this.isInitMultiArray) {
return [
CHINA_REGIONS,
CHINA_REGIONS[0].childs,
CHINA_REGIONS[0].childs[0].childs
];
}
return [CHINA_REGIONS, this.cityArr, this.districtArr];
}
},
methods: {
handleColumnChange(e) {
// console.log(e);
this.isInitMultiArray = false;
const that = this;
let col = e.detail.column;
let row = e.detail.value;
that.multiIndex[col] = row;
try {
switch (col) {
case 0:
if (CHINA_REGIONS[that.multiIndex[0]].childs.length == 0) {
that.cityArr = that.districtArr = [
CHINA_REGIONS[that.multiIndex[0]]
];
break;
}
that.cityArr = CHINA_REGIONS[that.multiIndex[0]].childs;
that.districtArr =
CHINA_REGIONS[that.multiIndex[0]].childs[
that.multiIndex[1]
].childs;
break;
case 1:
that.districtArr =
CHINA_REGIONS[that.multiIndex[0]].childs[
that.multiIndex[1]
].childs;
break;
case 2:
break;
}
} catch (e) {
// console.log(e);
that.districtArr = CHINA_REGIONS[that.multiIndex[0]].childs[0].childs;
}
},
handleValueChange(e) {
//
let [index0, index1, index2] = e.detail.value;
let [arr0, arr1, arr2] = this.pickedArr;
let address = [arr0[index0], arr1[index1], arr2[index2]];
// console.log(address);
this.$emit('getRegion', address);
this.$emit(
'input',
`${address[0].name}${address[1].name}${address[2].name}`
);
},
handleDefaultRegion(region) {
const isCode = !Array.isArray(region);
this.isInitMultiArray = false;
let children = CHINA_REGIONS;
for (let i = 0; i < 3; i++) {
for (let j = 0; j < children.length; j++) {
let condition = isCode
? children[j].code == region.slice(0, (i + 1) * 2)
: children[j].name.includes(region[i]);
if (condition) {
//
// console.log(i,j,children.length-1);
children = children[j].childs;
if (i == 0) {
this.cityArr = children;
} else if (i == 1) {
this.districtArr = children;
}
this.$set(this.multiIndex, i, j);
// console.log(this.multiIndex);
break;
} else {
//
// console.log(i,j,children.length-1);
if (i == 0 && j == children.length - 1) {
this.isInitMultiArray = true;
}
}
}
}
}
}
};
</script>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,212 @@
<template>
<view class="">
<rider-swipe-action :actions="option.actions" @click="onActionClick($event, nodeIndex, list)">
<template v-slot:content>
<view class="crud flex flex-c">
<view class="crud-checkbox">
<checkbox-group @change="e => onChecked(e, node.id)" v-if="checkShow">
<label>
<checkbox
style="transform: scale(0.7)"
:value="node.id"
:checked="checkList.includes(node.id)"
class="curdCheck"
></checkbox>
</label>
</checkbox-group>
</view>
<view class="flex-one" @click.stop="onActionClick({ index: -1 }, nodeIndex, list)">
<view v-for="(col, i) in option.column" :key="i" class="crud-row flex">
<view class="crud-row-label">{{ col.label }}</view>
<view class="crud-row-value">{{ node[col.prop] }}</view>
</view>
</view>
</view>
<block v-if="node.hasChildren || (node.children && node.children.length > 0)">
<view class="crud-child" @click="onToggle(node, nodeIndex)">
{{ !collapsed ? '查看下级' : '收起' }}
<u-icon :name="`arrow-${!collapsed ? 'down' : 'up'}`"></u-icon>
</view>
</block>
</template>
</rider-swipe-action>
<!-- 下级 -->
<template v-if="node.hasChildren || (node.children && node.children.length > 0)">
<!-- <block v-if="collapsed">
<view class="" v-for="(item, index) in node.children" :key="index">
<crud-item :ref="`crud-itemc${index}`" :node="item" :list="node.children" :option="option" :checkShow="checkShow" :nodeIndex="index" :checkList="checkList"></crud-item>
</view>
</block> -->
<u-popup v-model="collapsed" mode="bottom" closeable :mask-close-able="false" :z-index="zIndex">
<view class="form-head"><u-section title="查看下级" :right="false" line-color="#5f88ff"></u-section></view>
<scroll-view scroll-y="true" style="height: 75vh; background: #f5f5f5">
<crud-item
v-for="(item, index) in node.children"
:key="index"
:ref="`crud-item${index}`"
:node="item"
:list="node.children"
:option="option"
:checkShow="checkShow"
:nodeIndex="index"
:checkList="checkList"
:random-num="randomNum"
:z-index="zIndex + 1"
></crud-item>
</scroll-view>
</u-popup>
</template>
</view>
</template>
<script>
import riderSwipeAction from '../swipe-action/index'
// #ifdef MP
import crudItem from '@/components/rider-crud/components/crud-item/index'
// #endif
export default {
name: 'crud-item',
components: {
riderSwipeAction,
// #ifndef MP
crudItem: () => import('@/components/rider-crud/components/crud-item/index'),
// #endif
// #ifdef MP
crudItem
// #endif
},
props: {
//
node: {
type: Object,
default: {}
},
list: Array,
//
nodeIndex: {
type: Number,
default: 0
},
//
option: {
type: Object,
default: {
column: [],
manageSwitch: true
}
},
checkList: Array,
checkShow: {
type: Boolean,
default: false
},
randomNum: Number,
zIndex: {
type: Number,
default: 10075
}
},
data() {
return {
collapsed: false
}
},
methods: {
setLazyLoad(func) {
this.option.lazyLoad = func
},
onActionClick({ index }, i, list) {
uni.$emit(`action${this.randomNum}`, { index }, i, list)
},
onChecked(e, id) {
uni.$emit(`checked${this.randomNum}`, e, id)
},
onToggle(item, index) {
if (item.hasChildren && this.collapsed == false) {
if (!item.children || item.children.length == 0) {
// #ifdef MP
uni.showToast({
title: '小程序暂不支持懒加载查看下级',
icon: 'none'
})
return
// #endif
if (typeof this.option.lazyLoad == 'function') {
const cb = list => {
this.$set(item, 'children', list)
this.collapsed = true
}
this.option.lazyLoad(item, index, cb)
}
} else this.collapsed = true
setTimeout(() => {
if (this.node.children)
this.node.children.forEach((l, i) => {
this.$refs[`crud-item${i}`][0].setLazyLoad(this.option.lazyLoad)
})
}, 100)
} else {
this.collapsed = !this.collapsed
}
},
hide() {
this.collapsed = false
}
}
}
</script>
<style scoped lang="scss">
.flex {
display: flex;
justify-content: space-between;
}
.flex-c {
align-items: center;
}
.flex-one {
flex: 1;
}
.form-head {
padding: 36rpx 30rpx;
border-bottom: 2rpx dashed #efefef;
}
.crud {
background: #fff;
padding: 26rpx;
border-radius: 8rpx;
.crud-row + .crud-row {
margin-top: 14rpx;
}
&-child {
color: #5f88ff;
font-size: 30rpx;
text-align: center;
padding: 20rpx 0;
border-top: 2rpx dashed #f5f5f5;
}
&-row {
&-label {
color: #999;
font-size: 28rpx;
margin-right: 6rpx;
}
&-value {
flex: 1;
color: #565656;
font-size: 28rpx;
word-break: break-all;
word-wrap: break-word;
white-space: pre-wrap;
}
}
}
</style>

View File

@ -0,0 +1,11 @@
// 以下项目可以删减或更换顺序,但不能添加或更改名字
export default {
// 普通标签的菜单项
node: ['大小', '斜体', '粗体', '下划线', '居中', '缩进', '上移', '下移', '删除'],
// 图片的菜单项
img: ['换图', '宽度', '超链接', '预览图', '禁用预览', '上移', '下移', '删除'],
// 链接的菜单项
link: ['更换链接', '上移', '下移', '删除'],
// 音视频的菜单项
media: ['封面', '循环', '自动播放', '上移', '下移', '删除']
}

View File

@ -0,0 +1,532 @@
/**
* @fileoverview editable 插件
*/
import config from './config'
import Parser from '../parser'
function Editable (vm) {
this.vm = vm
this.editHistory = [] // 历史记录
this.editI = -1 // 历史记录指针
vm._mask = [] // 蒙版被点击时进行的操作
vm._setData = function (path, val) {
const paths = path.split('.')
let target = vm
for (let i = 0; i < paths.length - 1; i++) {
target = target[paths[i]]
}
vm.$set(target, paths.pop(), val)
}
/**
* @description 移动历史记录指针
* @param {Number} num 移动距离
*/
const move = num => {
setTimeout(() => {
const item = this.editHistory[this.editI + num]
if (item) {
this.editI += num
vm._setData(item.key, item.value)
}
}, 200)
}
vm.undo = () => move(-1) // 撤销
vm.redo = () => move(1) // 重做
/**
* @description 更新记录
* @param {String} path 更新内容路径
* @param {*} oldVal 旧值
* @param {*} newVal 新值
* @param {Boolean} set 是否更新到视图
* @private
*/
vm._editVal = (path, oldVal, newVal, set) => {
// 当前指针后的内容去除
while (this.editI < this.editHistory.length - 1) {
this.editHistory.pop()
}
// 最多存储 30 条操作记录
while (this.editHistory.length > 30) {
this.editHistory.pop()
this.editI--
}
const last = this.editHistory[this.editHistory.length - 1]
if (!last || last.key !== path) {
if (last) {
// 去掉上一次的新值
this.editHistory.pop()
this.editI--
}
// 存入这一次的旧值
this.editHistory.push({
key: path,
value: oldVal
})
this.editI++
}
// 存入本次的新值
this.editHistory.push({
key: path,
value: newVal
})
this.editI++
// 更新到视图
if (set) {
vm._setData(path, newVal)
}
}
/**
* @description 获取菜单项
* @private
*/
vm._getItem = function (node, up, down) {
let items
let i
if (node.name === 'img') {
items = config.img.slice(0)
if (!vm.getSrc) {
i = items.indexOf('换图')
if (i !== -1) {
items.splice(i, 1)
}
i = items.indexOf('超链接')
if (i !== -1) {
items.splice(i, 1)
}
i = items.indexOf('预览图')
if (i !== -1) {
items.splice(i, 1)
}
}
i = items.indexOf('禁用预览')
if (i !== -1 && node.attrs.ignore) {
items[i] = '启用预览'
}
} else if (node.name === 'a') {
items = config.link.slice(0)
if (!vm.getSrc) {
i = items.indexOf('更换链接')
if (i !== -1) {
items.splice(i, 1)
}
}
} else if (node.name === 'video' || node.name === 'audio') {
items = config.media.slice(0)
i = items.indexOf('封面')
if (!vm.getSrc && i !== -1) {
items.splice(i, 1)
}
i = items.indexOf('循环')
if (node.attrs.loop && i !== -1) {
items[i] = '不循环'
}
i = items.indexOf('自动播放')
if (node.attrs.autoplay && i !== -1) {
items[i] = '不自动播放'
}
} else {
items = config.node.slice(0)
}
if (!up) {
i = items.indexOf('上移')
if (i !== -1) {
items.splice(i, 1)
}
}
if (!down) {
i = items.indexOf('下移')
if (i !== -1) {
items.splice(i, 1)
}
}
return items
}
/**
* @description 显示 tooltip
* @param {object} obj
* @private
*/
vm._tooltip = function (obj) {
vm.$set(vm, 'tooltip', {
top: obj.top,
items: obj.items
})
vm._tooltipcb = obj.success
}
/**
* @description 显示滚动条
* @param {object} obj
* @private
*/
vm._slider = function (obj) {
vm.$set(vm, 'slider', {
min: obj.min,
max: obj.max,
value: obj.value,
top: obj.top
})
vm._slideringcb = obj.changing
vm._slidercb = obj.change
}
/**
* @description 点击蒙版
* @private
*/
vm._maskTap = function () {
// 隐藏所有悬浮窗
while (vm._mask.length) {
(vm._mask.pop())()
}
if (vm.tooltip) {
vm.$set(vm, 'tooltip', null)
}
if (vm.slider) {
vm.$set(vm, 'slider', null)
}
}
/**
* @description 插入节点
* @param {Object} node
*/
function insert (node) {
if (vm._edit) {
vm._edit.insert(node)
} else {
const nodes = vm.nodes.slice(0)
nodes.push(node)
vm._editVal('nodes', vm.nodes, nodes, true)
}
}
/**
* @description 在光标处插入指定 html 内容
* @param {String} html 内容
*/
vm.insertHtml = html => {
this.inserting = true
const arr = new Parser(vm).parse(html)
this.inserting = undefined
for (let i = 0; i < arr.length; i++) {
insert(arr[i])
}
}
/**
* @description 在光标处插入图片
*/
vm.insertImg = function () {
vm.getSrc && vm.getSrc('img').then(src => {
if (typeof src === 'string') {
src = [src]
}
const parser = new Parser(vm)
for (let i = 0; i < src.length; i++) {
insert({
name: 'img',
attrs: {
src: parser.getUrl(src[i])
}
})
}
}).catch(() => { })
}
/**
* @description 在光标处插入一个链接
*/
vm.insertLink = function () {
vm.getSrc && vm.getSrc('link').then(url => {
insert({
name: 'a',
attrs: {
href: url
},
children: [{
type: 'text',
text: url
}]
})
}).catch(() => { })
}
/**
* @description 在光标处插入一个表格
* @param {Number} rows 行数
* @param {Number} cols 列数
*/
vm.insertTable = function (rows, cols) {
const table = {
name: 'table',
attrs: {
style: 'display:table;width:100%;margin:10px 0;text-align:center;border-spacing:0;border-collapse:collapse;border:1px solid gray'
},
children: []
}
for (let i = 0; i < rows; i++) {
const tr = {
name: 'tr',
attrs: {},
children: []
}
for (let j = 0; j < cols; j++) {
tr.children.push({
name: 'td',
attrs: {
style: 'padding:2px;border:1px solid gray'
},
children: [{
type: 'text',
text: ''
}]
})
}
table.children.push(tr)
}
insert(table)
}
/**
* @description 插入视频/音频
* @param {Object} node
*/
function insertMedia (node) {
if (typeof node.src === 'string') {
node.src = [node.src]
}
const parser = new Parser(vm)
// 拼接主域名
for (let i = 0; i < node.src.length; i++) {
node.src[i] = parser.getUrl(node.src[i])
}
insert({
name: 'div',
attrs: {
style: 'text-align:center'
},
children: [node]
})
}
/**
* @description 在光标处插入一个视频
*/
vm.insertVideo = function () {
vm.getSrc && vm.getSrc('video').then(src => {
insertMedia({
name: 'video',
attrs: {
controls: 'T'
},
children: [],
src,
// #ifdef APP-PLUS
html: `<video src="${src}" style="width:100%;height:100%"></video>`
// #endif
})
}).catch(() => { })
}
/**
* @description 在光标处插入一个音频
*/
vm.insertAudio = function () {
vm.getSrc && vm.getSrc('audio').then(attrs => {
let src
if (attrs.src) {
src = attrs.src
attrs.src = undefined
} else {
src = attrs
attrs = {}
}
attrs.controls = 'T'
insertMedia({
name: 'audio',
attrs,
children: [],
src
})
}).catch(() => { })
}
/**
* @description 在光标处插入一段文本
*/
vm.insertText = function () {
insert({
name: 'p',
attrs: {},
children: [{
type: 'text',
text: ''
}]
})
}
/**
* @description 清空内容
*/
vm.clear = function () {
vm._maskTap()
vm._edit = undefined
vm.$set(vm, 'nodes', [{
name: 'p',
attrs: {},
children: [{
type: 'text',
text: ''
}]
}])
}
/**
* @description 获取编辑后的 html
*/
vm.getContent = function () {
let html = '';
// 递归遍历获取
(function traversal (nodes, table) {
for (let i = 0; i < nodes.length; i++) {
let item = nodes[i]
if (item.type === 'text') {
html += item.text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br>').replace(/\xa0/g, '&nbsp;') // 编码实体
} else {
if (item.name === 'img') {
item.attrs.i = ''
// 还原被转换的 svg
if ((item.attrs.src || '').includes('data:image/svg+xml;utf8,')) {
html += item.attrs.src.substr(24).replace(/%23/g, '#').replace('<svg', '<svg style="' + (item.attrs.style || '') + '"')
continue
}
} else if (item.name === 'video' || item.name === 'audio') {
// 还原 video 和 audio 的 source
item = JSON.parse(JSON.stringify(item))
if (item.src.length > 1) {
item.children = []
for (let j = 0; j < item.src.length; j++) {
item.children.push({
name: 'source',
attrs: {
src: item.src[j]
}
})
}
} else {
item.attrs.src = item.src[0]
}
} else if (item.name === 'div' && (item.attrs.style || '').includes('overflow:auto') && (item.children[0] || {}).name === 'table') {
// 还原滚动层
item = item.children[0]
}
// 还原 table
if (item.name === 'table') {
item = JSON.parse(JSON.stringify(item))
table = item.attrs
if ((item.attrs.style || '').includes('display:grid')) {
item.attrs.style = item.attrs.style.split('display:grid')[0]
const children = [{
name: 'tr',
attrs: {},
children: []
}]
for (let j = 0; j < item.children.length; j++) {
item.children[j].attrs.style = item.children[j].attrs.style.replace(/grid-[^;]+;*/g, '')
if (item.children[j].r !== children.length) {
children.push({
name: 'tr',
attrs: {},
children: [item.children[j]]
})
} else {
children[children.length - 1].children.push(item.children[j])
}
}
item.children = children
}
}
html += '<' + item.name
for (const attr in item.attrs) {
let val = item.attrs[attr]
if (!val) continue
if (val === 'T' || val === true) {
// bool 型省略值
html += ' ' + attr
continue
} else if (item.name[0] === 't' && attr === 'style' && table) {
// 取消为了显示 table 添加的 style
val = val.replace(/;*display:table[^;]*/, '')
if (table.border) {
val = val.replace(/border[^;]+;*/g, $ => $.includes('collapse') ? $ : '')
}
if (table.cellpadding) {
val = val.replace(/padding[^;]+;*/g, '')
}
if (!val) continue
}
html += ' ' + attr + '="' + val.replace(/"/g, '&quot;') + '"'
}
html += '>'
if (item.children) {
traversal(item.children, table)
html += '</' + item.name + '>'
}
}
}
})(vm.nodes)
// 其他插件处理
for (let i = vm.plugins.length; i--;) {
if (vm.plugins[i].onGetContent) {
html = vm.plugins[i].onGetContent(html) || html
}
}
return html
}
}
Editable.prototype.onUpdate = function (content, config) {
if (this.vm.editable) {
this.vm._maskTap()
config.entities.amp = '&'
if (!this.inserting) {
this.vm._edit = undefined
if (!content) {
setTimeout(() => {
this.vm.$set(this.vm, 'nodes', [{
name: 'p',
attrs: {},
children: [{
type: 'text',
text: ''
}]
}])
}, 0)
}
}
}
}
Editable.prototype.onParse = function (node) {
// 空白单元格可编辑
if (this.vm.editable && (node.name === 'td' || node.name === 'th') && !this.vm.getText(node.children)) {
node.children.push({
type: 'text',
text: ''
})
}
}
export default Editable

View File

@ -0,0 +1,540 @@
<template>
<view id="_root" :class="(selectable?'_select ':'')+'_root'" :style="(editable?'min-height:200px;':'')+containerStyle" @tap="_containTap">
<slot v-if="!nodes[0]" />
<!-- #ifndef APP-PLUS-NVUE -->
<node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu,selectable,editable,placeholder,'nodes']" name="span" />
<!-- #endif -->
<!-- #ifdef APP-PLUS-NVUE -->
<web-view ref="web" src="/static/app-plus/mp-html/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" />
<!-- #endif -->
<view v-if="tooltip" class="_tooltip_contain" :style="'top:'+tooltip.top+'px'">
<view class="_tooltip">
<view v-for="(item, index) in tooltip.items" v-bind:key="index" class="_tooltip_item" :data-i="index" @tap="_tooltipTap">{{item}}</view>
</view>
</view>
<view v-if="slider" class="_slider" :style="'top:'+slider.top+'px'">
<slider :value="slider.value" :min="slider.min" :max="slider.max" handle-size="14" block-size="14" show-value activeColor="white" style="padding:3px" @changing="_sliderChanging" @change="_sliderChange" />
</view>
</view>
</template>
<script>
/**
* mp-html v2.3.2
* @description 富文本组件
* @tutorial https://github.com/jin-yufeng/mp-html
* @property {String} container-style 容器的样式
* @property {String} content 用于渲染的 html 字符串
* @property {Boolean} copy-link 是否允许外部链接被点击时自动复制
* @property {String} domain 主域名用于拼接链接
* @property {String} error-img 图片出错时的占位图链接
* @property {Boolean} lazy-load 是否开启图片懒加载
* @property {string} loading-img 图片加载过程中的占位图链接
* @property {Boolean} pause-video 是否在播放一个视频时自动暂停其他视频
* @property {Boolean} preview-img 是否允许图片被点击时自动预览
* @property {Boolean} scroll-table 是否给每个表格添加一个滚动层使其能单独横向滚动
* @property {Boolean | String} selectable 是否开启长按复制
* @property {Boolean} set-title 是否将 title 标签的内容设置到页面标题
* @property {Boolean} show-img-menu 是否允许图片被长按时显示菜单
* @property {Object} tag-style 标签的默认样式
* @property {Boolean | Number} use-anchor 是否使用锚点链接
* @event {Function} load dom 结构加载完毕时触发
* @event {Function} ready 所有图片加载完毕时触发
* @event {Function} imgtap 图片被点击时触发
* @event {Function} linktap 链接被点击时触发
* @event {Function} play 音视频播放时触发
* @event {Function} error 媒体加载出错时触发
*/
// #ifndef APP-PLUS-NVUE
import node from './node/node'
// #endif
import Parser from './parser'
import editable from './editable/index.js'
const plugins=[editable,]
// #ifdef APP-PLUS-NVUE
const dom = weex.requireModule('dom')
// #endif
export default {
name: 'mp-html',
data() {
return {
tooltip: null,
slider: null,
nodes: [],
// #ifdef APP-PLUS-NVUE
height: 3
// #endif
}
},
props: {
editable: Boolean,
placeholder: String,
containerStyle: {
type: String,
default: ''
},
content: {
type: String,
default: ''
},
copyLink: {
type: [Boolean, String],
default: true
},
domain: String,
errorImg: {
type: String,
default: ''
},
lazyLoad: {
type: [Boolean, String],
default: false
},
loadingImg: {
type: String,
default: ''
},
pauseVideo: {
type: [Boolean, String],
default: true
},
previewImg: {
type: [Boolean, String],
default: true
},
scrollTable: [Boolean, String],
selectable: [Boolean, String],
setTitle: {
type: [Boolean, String],
default: true
},
showImgMenu: {
type: [Boolean, String],
default: true
},
tagStyle: Object,
useAnchor: [Boolean, Number]
},
// #ifdef VUE3
emits: ['load', 'ready', 'imgtap', 'linktap', 'play', 'error'],
// #endif
// #ifndef APP-PLUS-NVUE
components: {
node
},
// #endif
watch: {
editable(val) {
this.setContent(val ? this.content : this.getContent())
if (!val)
this._maskTap()
},
content (content) {
this.setContent(content)
}
},
created () {
this.plugins = []
for (let i = plugins.length; i--;) {
this.plugins.push(new plugins[i](this))
}
},
mounted () {
if ((this.content || this.editable) && !this.nodes.length) {
this.setContent(this.content)
}
},
beforeDestroy () {
this._hook('onDetached')
clearInterval(this._timer)
},
methods: {
_containTap() {
if (!this._lock && !this.slider) {
this._edit = undefined
this._maskTap()
}
},
_tooltipTap(e) {
this._tooltipcb(e.currentTarget.dataset.i)
this.$set(this, 'tooltip', null)
},
_sliderChanging(e) {
this._slideringcb(e.detail.value)
},
_sliderChange(e) {
this._slidercb(e.detail.value)
},
/**
* @description 将锚点跳转的范围限定在一个 scroll-view
* @param {Object} page scroll-view 所在页面的示例
* @param {String} selector scroll-view 的选择器
* @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名
*/
in (page, selector, scrollTop) {
// #ifndef APP-PLUS-NVUE
if (page && selector && scrollTop) {
this._in = {
page,
selector,
scrollTop
}
}
// #endif
},
/**
* @description 锚点跳转
* @param {String} id 要跳转的锚点 id
* @param {Number} offset 跳转位置的偏移量
* @returns {Promise}
*/
navigateTo (id, offset) {
return new Promise((resolve, reject) => {
if (!this.useAnchor) {
reject(Error('Anchor is disabled'))
return
}
offset = offset || parseInt(this.useAnchor) || 0
// #ifdef APP-PLUS-NVUE
if (!id) {
dom.scrollToElement(this.$refs.web, {
offset
})
resolve()
} else {
this._navigateTo = {
resolve,
reject,
offset
}
this.$refs.web.evalJs('uni.postMessage({data:{action:"getOffset",offset:(document.getElementById(' + id + ')||{}).offsetTop}})')
}
// #endif
// #ifndef APP-PLUS-NVUE
let deep = ' '
// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
deep = '>>>'
// #endif
const selector = uni.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(this._in ? this._in.page : this)
// #endif
.select((this._in ? this._in.selector : '._root') + (id ? `${deep}#${id}` : '')).boundingClientRect()
if (this._in) {
selector.select(this._in.selector).scrollOffset()
.select(this._in.selector).boundingClientRect()
} else {
// scroll-view
selector.selectViewport().scrollOffset() //
}
selector.exec(res => {
if (!res[0]) {
reject(Error('Label not found'))
return
}
const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + offset
if (this._in) {
// scroll-view
this._in.page[this._in.scrollTop] = scrollTop
} else {
//
uni.pageScrollTo({
scrollTop,
duration: 300
})
}
resolve()
})
// #endif
})
},
/**
* @description 获取文本内容
* @return {String}
*/
getText (nodes) {
let text = '';
(function traversal (nodes) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
if (node.type === 'text') {
text += node.text.replace(/&amp;/g, '&')
} else if (node.name === 'br') {
text += '\n'
} else {
//
const isBlock = node.name === 'p' || node.name === 'div' || node.name === 'tr' || node.name === 'li' || (node.name[0] === 'h' && node.name[1] > '0' && node.name[1] < '7')
if (isBlock && text && text[text.length - 1] !== '\n') {
text += '\n'
}
//
if (node.children) {
traversal(node.children)
}
if (isBlock && text[text.length - 1] !== '\n') {
text += '\n'
} else if (node.name === 'td' || node.name === 'th') {
text += '\t'
}
}
}
})(nodes || this.nodes)
return text
},
/**
* @description 获取内容大小和位置
* @return {Promise}
*/
getRect () {
return new Promise((resolve, reject) => {
uni.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(this)
// #endif
.select('#_root').boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject(Error('Root label not found')))
})
},
/**
* @description 暂停播放媒体
*/
pauseMedia () {
for (let i = (this._videos || []).length; i--;) {
this._videos[i].pause()
}
// #ifdef APP-PLUS
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].pause()'
// #ifndef APP-PLUS-NVUE
let page = this.$parent
while (!page.$scope) page = page.$parent
page.$scope.$getAppWebview().evalJS(command)
// #endif
// #ifdef APP-PLUS-NVUE
this.$refs.web.evalJs(command)
// #endif
// #endif
},
/**
* @description 设置内容
* @param {String} content html 内容
* @param {Boolean} append 是否在尾部追加
*/
setContent (content, append) {
if (!append || !this.imgList) {
this.imgList = []
}
const nodes = new Parser(this).parse(content)
// #ifdef APP-PLUS-NVUE
if (this._ready) {
this._set(nodes, append)
}
// #endif
this.$set(this, 'nodes', append ? (this.nodes || []).concat(nodes) : nodes)
// #ifndef APP-PLUS-NVUE
this._videos = []
this.$nextTick(() => {
this._hook('onLoad')
this.$emit('load')
})
//
let height
clearInterval(this._timer)
this._timer = setInterval(() => {
this.getRect().then(rect => {
// 350ms ready
if (rect.height === height) {
this.$emit('ready', rect)
clearInterval(this._timer)
}
height = rect.height
}).catch(() => { })
}, 350)
// #endif
},
/**
* @description 调用插件钩子函数
*/
_hook (name) {
for (let i = plugins.length; i--;) {
if (this.plugins[i][name]) {
this.plugins[i][name]()
}
}
},
// #ifdef APP-PLUS-NVUE
/**
* @description 设置内容
*/
_set (nodes, append) {
this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes) + ',' + JSON.stringify([this.containerStyle.replace(/(?:margin|padding)[^;]+/g, ''), this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')')
},
/**
* @description 接收到 web-view 消息
*/
_onMessage (e) {
const message = e.detail.data[0]
switch (message.action) {
// web-view
case 'onJSBridgeReady':
this._ready = true
if (this.nodes) {
this._set(this.nodes)
}
break
// dom
case 'onLoad':
this.height = message.height
this._hook('onLoad')
this.$emit('load')
break
//
case 'onReady':
this.getRect().then(res => {
this.$emit('ready', res)
}).catch(() => { })
break
//
case 'onHeightChange':
this.height = message.height
break
//
case 'onImgTap':
this.$emit('imgtap', message.attrs)
if (this.previewImg) {
uni.previewImage({
current: parseInt(message.attrs.i),
urls: this.imgList
})
}
break
//
case 'onLinkTap': {
const href = message.attrs.href
this.$emit('linktap', message.attrs)
if (href) {
//
if (href[0] === '#') {
if (this.useAnchor) {
dom.scrollToElement(this.$refs.web, {
offset: message.offset
})
}
} else if (href.includes('://')) {
//
if (this.copyLink) {
plus.runtime.openWeb(href)
}
} else {
uni.navigateTo({
url: href,
fail () {
uni.switchTab({
url: href
})
}
})
}
}
break
}
case 'onPlay':
this.$emit('play')
break
//
case 'getOffset':
if (typeof message.offset === 'number') {
dom.scrollToElement(this.$refs.web, {
offset: message.offset + this._navigateTo.offset
})
this._navigateTo.resolve()
} else {
this._navigateTo.reject(Error('Label not found'))
}
break
//
case 'onClick':
this.$emit('tap')
this.$emit('click')
break
//
case 'onError':
this.$emit('error', {
source: message.source,
attrs: message.attrs
})
}
}
// #endif
}
}
</script>
<style>
/* #ifndef APP-PLUS-NVUE */
/* 根节点样式 */
._root {
padding: 1px 0;
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
}
/* 长按复制 */
._select {
user-select: text;
}
/* #endif */
/* 提示条 */
._tooltip_contain {
position: absolute;
right: 20px;
left: 20px;
text-align: center;
}
._tooltip {
box-sizing: border-box;
display: inline-block;
width: auto;
max-width: 100%;
height: 30px;
padding: 0 3px;
overflow: scroll;
font-size: 14px;
line-height: 30px;
white-space: nowrap;
}
._tooltip_item {
display: inline-block;
width: auto;
padding: 0 2vw;
line-height: 30px;
background-color: black;
color: white;
}
/* 图片宽度滚动条 */
._slider {
position: absolute;
left: 20px;
width: 220px;
}
._tooltip,
._slider {
background-color: black;
border-radius: 3px;
opacity: 0.75;
}
</style>

View File

@ -0,0 +1,972 @@
<template>
<view @tap="nodeTap" :id="attrs.id" :class="'_block _'+name+' '+attrs.class" :style="(ctrl.root?'border:1px solid black;padding:5px;display:block;':'')+attrs.style">
<block v-for="(n, i) in childs" v-bind:key="i">
<!-- 图片 -->
<!-- 占位图 -->
<image v-if="n.name==='img'&&((opts[1]&&!ctrl[i])||ctrl[i]<0)" class="_img" :style="n.attrs.style" :src="ctrl[i]<0?opts[2]:opts[1]" mode="widthFix" />
<!-- 显示图片 -->
<!-- #ifdef H5 || (APP-PLUS && VUE2) -->
<img v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl['e'+i]?'border:1px dashed black;padding:3px;':'')+(ctrl[i]===-1?'display:none;':'')+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
<!-- #endif -->
<!-- #ifndef H5 || APP-PLUS -->
<image v-if="n.name==='img'" :id="n.attrs.id||('n'+i)" :class="'_img '+n.attrs.class" :style="(ctrl['e'+i]?'border:1px dashed black;padding:3px;':'')+(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:'+(ctrl['h'+i]||1)+'px;'+n.attrs.style" :src="n.attrs.src" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :lazy-load="opts[0]" :webp="n.webp" :show-menu-by-longpress="!opts[5]&&opts[3]&&!n.attrs.ignore" :image-menu-prevent="opts[5]||!opts[3]||n.attrs.ignore" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
<!-- #endif -->
<!-- #ifdef APP-PLUS && VUE3 -->
<image v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl['e'+i]?'border:1px dashed black;padding:3px;':'')+(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;'+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
<!-- #endif -->
<!-- 文本 -->
<text v-else-if="n.type==='text'&&!ctrl['e'+i]" :data-i="i" :user-select="opts[4]" :decode="!opts[5]" @tap="editStart">{{n.text}}
<text v-if="!n.text" style="color:gray">{{opts[6]||'请输入'}}</text>
</text>
<text v-else-if="n.type==='text'&&ctrl['e'+i]===1" :data-i="i" style="border:1px dashed black;min-width:50px;width:auto;padding:5px;display:block" @tap.stop="editStart">{{n.text}}
<text v-if="!n.text" style="color:gray">{{opts[6]||'请输入'}}</text>
</text>
<textarea v-else-if="n.type==='text'" style="border:1px dashed black;min-width:50px;width:auto;padding:5px" auto-height maxlength="-1" :focus="ctrl['e'+i]===3" :value="n.text" :data-i="i" @input="editInput" @blur="editEnd" />
<text v-else-if="n.name==='br'">\n</text>
<!-- 链接 -->
<view v-else-if="n.name==='a'" :id="n.attrs.id" :class="(n.attrs.href?'_a ':'')+n.attrs.class" hover-class="_hover" :style="'display:inline;'+n.attrs.style" :data-i="i" @tap.stop="linkTap">
<node name="span" :childs="n.children" :opts="[opts[0],opts[1],opts[2],opts[3],opts[4],opts[5],opts[6],opts[7]+'.'+i+'.children']" style="display:inherit" />
</view>
<!-- 视频 -->
<!-- #ifdef APP-PLUS -->
<view v-else-if="n.html" :data-i="i" @tap="mediaTap" :id="n.attrs.id" :class="'_video '+n.attrs.class" :style="n.attrs.style" v-html="n.html" @vplay.stop="play" />
<!-- #endif -->
<!-- #ifndef APP-PLUS -->
<video :show-center-play-btn="!opts[5]" @tap="mediaTap" v-else-if="n.name==='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :object-fit="n.attrs['object-fit']" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
<!-- #endif -->
<!-- #ifdef H5 || APP-PLUS -->
<iframe v-else-if="n.name==='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder" :src="n.attrs.src" />
<embed v-else-if="n.name==='embed'" :style="n.attrs.style" :src="n.attrs.src" />
<!-- #endif -->
<!-- #ifndef MP-TOUTIAO || ((H5 || APP-PLUS) && VUE3) -->
<!-- 音频 -->
<audio @tap="mediaTap" v-else-if="n.name==='audio'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
<!-- #endif -->
<view v-else-if="(n.name==='table'&&(n.c||opts[5]))||n.name==='li'" :id="n.attrs.id" :class="'_'+n.name+' '+n.attrs.class" :style="n.attrs.style">
<node v-if="n.name==='li'" :childs="n.children" :opts="[opts[0],opts[1],opts[2],opts[3],opts[4],opts[5],opts[6],opts[7]+'.'+i+'.children']" />
<view v-else v-for="(tbody, x) in n.children" v-bind:key="x" :class="'_'+tbody.name+' '+tbody.attrs.class" :style="tbody.attrs.style">
<node v-if="tbody.name==='td'||tbody.name==='th'" :childs="tbody.children" :opts="[opts[0],opts[1],opts[2],opts[3],opts[4],opts[5],opts[6],opts[7]+'.'+i+'.children.'+x+'.children']" />
<block v-else v-for="(tr, y) in tbody.children" v-bind:key="y">
<view v-if="tr.name==='td'||tr.name==='th'" :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
<node :childs="tr.children" :opts="[opts[0],opts[1],opts[2],opts[3],opts[4],opts[5],opts[6],opts[7]+'.'+i+'.children.'+x+'.children.'+y+'.children']" />
</view>
<view v-else :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
<view v-for="(td, z) in tr.children" v-bind:key="z" :class="'_'+td.name+' '+td.attrs.class" :style="td.attrs.style">
<node :childs="td.children" :opts="[opts[0],opts[1],opts[2],opts[3],opts[4],opts[5],opts[6],opts[7]+'.'+i+'.children.'+x+'.children.'+y+'.children.'+z+'.children']" />
</view>
</view>
</block>
</view>
</view>
<!-- 富文本 -->
<!-- #ifdef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
<rich-text v-else-if="!opts[5]&&!n.c&&!handler.isInline(n.name, n.attrs.style)" :id="n.attrs.id" :style="n.f" :user-select="opts[4]" :nodes="[n]" />
<!-- #endif -->
<!-- #ifndef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
<rich-text v-else-if="!opts[5]&&!n.c" :id="n.attrs.id" :style="n.f+';display:inline'" :preview="false" :selectable="opts[4]" :user-select="opts[4]" :nodes="[n]" />
<!-- #endif -->
<!-- 继续递归 -->
<view v-else-if="n.c===2" :id="n.attrs.id" :class="'_block _'+n.name+' '+n.attrs.class" :style="n.f+';'+n.attrs.style">
<node v-for="(n2, j) in n.children" v-bind:key="j" :style="n2.f" :name="n2.name" :attrs="n2.attrs" :childs="n2.children" :opts="[opts[0],opts[1],opts[2],opts[3],opts[4],opts[5],opts[6],opts[7]+'.'+i+'.children.'+j+'.children']" />
</view>
<node v-else :style="n.f" :name="n.name" :attrs="n.attrs" :childs="n.children" :opts="[opts[0],opts[1],opts[2],opts[3],opts[4],opts[5],opts[6],opts[7]+'.'+i+'.children']" />
</block>
</view>
</template>
<script module="handler" lang="wxs">
//
var inlineTags = {
abbr: true,
b: true,
big: true,
code: true,
del: true,
em: true,
i: true,
ins: true,
label: true,
q: true,
small: true,
span: true,
strong: true,
sub: true,
sup: true
}
/**
* @description 判断是否为行内标签
*/
module.exports = {
isInline: function (tagName, style) {
return inlineTags[tagName] || (style || '').indexOf('display:inline') !== -1
}
}
</script>
<script>
function getTop(e) {
let top
// #ifdef H5 || APP-PLUS
top = e.touches[0].pageY
// #endif
// #ifdef MP-ALIPAY
top = e.detail.pageY
// #endif
// #ifndef H5 || MP-ALIPAY || APP-PLUS
top = e.detail.y
// #endif
if (top - e.currentTarget.offsetTop < 150)
top = e.currentTarget.offsetTop
if (top < 30)
top += 70
return top - 30
}
import node from './node'
export default {
name: 'node',
options: {
// #ifdef MP-WEIXIN
virtualHost: true,
// #endif
// #ifdef MP-TOUTIAO
addGlobalClass: false
// #endif
},
data () {
return {
ctrl: {}
}
},
props: {
name: String,
attrs: {
type: Object,
default () {
return {}
}
},
childs: Array,
opts: Array
},
components: {
// #ifndef H5 && VUE3
node
// #endif
},
mounted () {
this.$nextTick(() => {
for (this.root = this.$parent; this.root.$options.name !== 'mp-html'; this.root = this.root.$parent);
})
// #ifdef H5 || APP-PLUS
if (this.opts[0]) {
let i
for (i = this.childs.length; i--;) {
if (this.childs[i].name === 'img') break
}
if (i !== -1) {
this.observer = uni.createIntersectionObserver(this).relativeToViewport({
top: 500,
bottom: 500
})
this.observer.observe('._img', res => {
if (res.intersectionRatio) {
this.$set(this.ctrl, 'load', 1)
this.observer.disconnect()
}
})
}
}
// #endif
},
beforeDestroy () {
if (this.root._edit === this) {
this.root._edit = undefined
}
// #ifdef H5 || APP-PLUS
if (this.observer) {
this.observer.disconnect()
}
// #endif
},
methods:{editStart (e) {
if (this.opts[5]) {
const i = e.currentTarget.dataset.i
if (!this.ctrl['e' + i]) {
// 线
this.$set(this.ctrl, 'e' + i, 1)
setTimeout(() => {
this.root._mask.push(() => this.$set(this.ctrl, 'e' + i, 0))
}, 50)
this.root._edit = this
this.i = i
this.cursor = this.childs[i].text.length
} else {
this.root._mask.pop()
this.root._maskTap()
// text textarea
this.$set(this.ctrl, 'e' + i, 2)
//
setTimeout(() => {
this.$set(this.ctrl, 'e' + i, 3)
}, 50)
}
}
},editInput (e) {
const i = e.target.dataset.i
//
const value = e.detail.value.replace(/ {2,}/, $ => {
let res = '\xa0'
for (let i = 1; i < $.length; i++) {
res += '\xa0'
}
return res
})
this.root._editVal(`${this.opts[7]}.${i}.text`, this.childs[i].text, value) //
this.cursor = e.detail.cursor
},editEnd (e) {
const i = e.target.dataset.i
this.$set(this.ctrl, 'e' + i, 0)
//
this.root._setData(`${this.opts[7]}.${i}.text`, e.detail.value.replace(/ {2}/g, '\xa0 '))
if (e.detail.cursor !== undefined) {
this.cursor = e.detail.cursor
}
},insert (node) {
setTimeout(() => {
const childs = this.childs.slice(0)
if (!childs[this.i]) {
childs.push(node)
} else if (childs[this.i].text) {
//
const text = childs[this.i].text
const list = []
if (this.cursor) {
list.push({
type: 'text',
text: text.substring(0, this.cursor)
})
}
list.push(node)
if (this.cursor < text.length) {
list.push({
type: 'text',
text: text.substring(this.cursor)
})
}
childs.splice(this.i, 1, ...list)
} else {
childs.splice(parseInt(this.i) + 1, 0, node)
}
this.root._editVal(this.opts[7], this.childs, childs, true)
this.i = parseInt(this.i) + 1
}, 200)
},remove (i) {
const arr = this.childs.slice(0)
const delEle = arr.splice(i, 1)[0]
if (delEle.name === 'img' || delEle.name === 'video' || delEle.name === 'audio') {
let src = delEle.attrs.src
if (delEle.src) {
src = delEle.src.length === 1 ? delEle.src[0] : delEle.src
}
this.root.$emit('remove', {
type: delEle.name,
src
})
}
this.root._edit = undefined
this.root._maskTap()
this.root._editVal(this.opts[7], this.childs, arr, true)
},nodeTap (e) {
if (this.opts[5]) {
if (this.root._lock) return
this.root._lock = true
setTimeout(() => {
this.root._lock = false
}, 50)
if (this.ctrl['e' + this.i] === 3) return
this.root._maskTap()
this.root._edit = this
let start = this.opts[7].lastIndexOf('children.')
if (start !== -1) {
start += 9
} else {
start = 6
}
const i = parseInt(this.opts[7].substring(start, this.opts[7].lastIndexOf('.children')))
let parent = this.$parent
while (parent && parent.$options.name !== 'node') {
parent = parent.$parent
}
if (!parent || this.opts[7].length - parent.opts[7].length > 15) return
// 线
this.$set(this.ctrl, 'root', 1)
this.root._mask.push(() => this.$set(this.ctrl, 'root', 0))
if (this.childs.length === 1 && this.childs[0].type === 'text' && !this.ctrl.e0) {
this.$set(this.ctrl, 'e0', 1)
this.root._mask.push(() => this.$set(this.ctrl, 'e0', 0))
this.i = 0
this.cursor = this.childs[0].text.length
}
const items = this.root._getItem(parent.childs[i], i !== 0, i !== parent.childs.length - 1)
this.root._tooltip({
top: getTop(e),
items,
success: tapIndex => {
if (items[tapIndex] === '大小') {
//
const style = parent.childs[i].attrs.style || ''
let value = style.match(/;font-size:([0-9]+)px/)
if (value) {
value = parseInt(value[1])
} else {
value = 16
}
this.root._slider({
min: 10,
max: 30,
value,
top: getTop(e),
changing: val => {
if (Math.abs(val - value) > 2) {
// 2
parent.changeStyle('font-size', i, val + 'px', value + 'px')
value = e.detail.value
}
},
change: val => {
if (val !== value) {
parent.changeStyle('font-size', i, val + 'px', value + 'px')
}
this.root._editVal(`${parent.opts[7]}.${i}.attrs.style`, style, parent.childs[i].attrs.style)
}
})
} else if (items[tapIndex] === '上移' || items[tapIndex] === '下移') {
const arr = parent.childs.slice(0)
const item = arr[i]
if (items[tapIndex] === '上移') {
arr[i] = arr[i - 1]
arr[i - 1] = item
} else {
arr[i] = arr[i + 1]
arr[i + 1] = item
}
this.root._editVal(parent.opts[7], parent.childs, arr, true)
} else if (items[tapIndex] === '删除') {
parent.remove(i)
} else {
const style = parent.childs[i].attrs.style || ''
let newStyle = ''
const item = items[tapIndex]
let name
let value
if (item === '斜体') {
name = 'font-style'
value = 'italic'
} else if (item === '粗体') {
name = 'font-weight'
value = 'bold'
} else if (item === '下划线') {
name = 'text-decoration'
value = 'underline'
} else if (item === '居中') {
name = 'text-align'
value = 'center'
} else if (item === '缩进') {
name = 'text-indent'
value = '2em'
}
if (style.includes(name + ':')) {
//
newStyle = style.replace(new RegExp(name + ':[^;]+'), '')
} else {
//
newStyle = style + ';' + name + ':' + value
}
this.root._editVal(`${parent.opts[7]}.${i}.attrs.style`, style, newStyle, true)
}
}
})
}
},mediaTap (e) {
if (this.opts[5]) {
const i = e.target.dataset.i
const node = this.childs[i]
const items = this.root._getItem(node)
this.root._edit = this
this.i = i
this.root._tooltip({
top: e.target.offsetTop - 30,
items,
success: tapIndex => {
switch (items[tapIndex]) {
case '封面':
//
this.root.getSrc('img', node.attrs.poster || '').then(url => {
this.root._editVal(`${this.opts[7]}.${i}.attrs.poster`, node.attrs.poster, url instanceof Array ? url[0] : url, true)
}).catch(() => { })
break
case '删除':
this.remove(i)
break
case '循环':
case '不循环':
//
this.root._setData(`${this.opts[7]}.${i}.attrs.loop`, !node.attrs.loop)
uni.showToast({
title: '成功'
})
break
case '自动播放':
case '不自动播放':
//
this.root._setData(`${this.opts[7]}.${i}.attrs.autoplay`, !node.attrs.autoplay)
uni.showToast({
title: '成功'
})
break
}
}
})
//
this.root._lock = true
setTimeout(() => {
this.root._lock = false
}, 50)
}
},changeStyle (name, i, value, oldVal) {
let style = this.childs[i].attrs.style || ''
if (style.includes(';' + name + ':' + oldVal)) {
// style
style = style.replace(';' + name + ':' + oldVal, ';' + name + ':' + value)
} else {
//
style += ';' + name + ':' + value
}
this.root._setData(`${this.opts[7]}.${i}.attrs.style`, style)
},
// #ifdef MP-WEIXIN
toJSON () { },
// #endif
/**
* @description 播放视频事件
* @param {Event} e
*/
play (e) {
this.root.$emit('play')
// #ifndef APP-PLUS
if (this.root.pauseVideo) {
let flag = false
const id = e.target.id
for (let i = this.root._videos.length; i--;) {
if (this.root._videos[i].id === id) {
flag = true
} else {
this.root._videos[i].pause() //
}
}
//
if (!flag) {
const ctx = uni.createVideoContext(id
// #ifndef MP-BAIDU
, this
// #endif
)
ctx.id = id
this.root._videos.push(ctx)
}
}
// #endif
},
/**
* @description 图片点击事件
* @param {Event} e
*/
imgTap (e) {
if (!this.opts[5]) {
const node = this.childs[e.currentTarget.dataset.i]
if (node.a) {
this.linkTap(node.a)
return
}
if (node.attrs.ignore) return
// #ifdef H5 || APP-PLUS
node.attrs.src = node.attrs.src || node.attrs['data-src']
// #endif
this.root.$emit('imgtap', node.attrs)
//
if (this.root.previewImg) {
uni.previewImage({
// #ifdef MP-WEIXIN
showmenu: this.root.showImgMenu,
// #endif
// #ifdef MP-ALIPAY
enablesavephoto: this.root.showImgMenu,
enableShowPhotoDownload: this.root.showImgMenu,
// #endif
current: parseInt(node.attrs.i),
urls: this.root.imgList
})
}
} else {
const i = e.currentTarget.dataset.i
const node = this.childs[i]
const items = this.root._getItem(node)
this.root._edit = this
this.i = i
this.root._maskTap()
this.$set(this.ctrl, 'e' + i, 1)
this.root._mask.push(() => this.$set(this.ctrl, 'e' + i, 0))
this.root._tooltip({
top: getTop(e),
items,
success: tapIndex => {
if (items[tapIndex] === '换图') {
//
this.root.getSrc('img', node.attrs.src || '').then(url => {
this.root._editVal(this.opts[7] + '.' + i + '.attrs.src', node.attrs.src, url instanceof Array ? url[0] : url, true)
}).catch(() => { })
} else if (items[tapIndex] === '宽度') {
//
const style = node.attrs.style || ''
let value = style.match(/max-width:([0-9]+)%/)
if (value) {
value = parseInt(value[1])
} else {
value = 100
}
this.root._slider({
min: 0,
max: 100,
value,
top: getTop(e),
changing: val => {
// 5%
if (Math.abs(val - value) > 5) {
this.changeStyle('max-width', i, val + '%', value + '%')
value = val
}
},
change: val => {
if (val !== value) {
this.changeStyle('max-width', i, val + '%', value + '%')
value = val
}
this.root._editVal(this.opts[7] + '.' + i + '.attrs.style', style, this.childs[i].attrs.style)
}
})
} else if (items[tapIndex] === '超链接') {
//
this.root.getSrc('link', node.a ? node.a.href : '').then(url => {
// a href
if (node.a) {
this.root._editVal(this.opts[7] + '.' + i + '.a.href', node.a.href, url, true)
} else {
const link = {
name: 'a',
attrs: {
href: url
},
children: [node]
}
node.a = link.attrs
this.root._editVal(this.opts[7] + '.' + i, node, link, true)
}
wx.showToast({
title: '成功'
})
}).catch(() => { })
} else if (items[tapIndex] === '预览图') {
//
this.root.getSrc('img', node.attrs['original-src'] || '').then(url => {
this.root._editVal(this.opts[7] + '.' + i + '.attrs.original-src', node.attrs['original-src'], url instanceof Array ? url[0] : url, true)
uni.showToast({
title: '成功'
})
}).catch(() => { })
} else if (items[tapIndex] === '删除') {
this.remove(i)
} else {
// /
this.root._setData(this.opts[7] + '.' + i + '.attrs.ignore', !node.attrs.ignore)
uni.showToast({
title: '成功'
})
}
}
})
this.root._lock = true
setTimeout(() => {
this.root._lock = false
}, 50)
}
},
/**
* @description 图片长按
*/
imgLongTap (e) {
// #ifdef APP-PLUS
const attrs = this.childs[e.currentTarget.dataset.i].attrs
if (this.opts[3] && !attrs.ignore) {
uni.showActionSheet({
itemList: ['保存图片'],
success: () => {
const save = path => {
uni.saveImageToPhotosAlbum({
filePath: path,
success () {
uni.showToast({
title: '保存成功'
})
}
})
}
if (this.root.imgList[attrs.i].startsWith('http')) {
uni.downloadFile({
url: this.root.imgList[attrs.i],
success: res => save(res.tempFilePath)
})
} else {
save(this.root.imgList[attrs.i])
}
}
})
}
// #endif
},
/**
* @description 图片加载完成事件
* @param {Event} e
*/
imgLoad(e) {
// #ifdef MP-WEIXIN || MP-QQ
if (this.opts[5])
this.$nextTick(() => {
const id = this.childs[i].attrs.id || ('n' + i)
uni.createSelectorQuery().in(this).select('#' + id).boundingClientRect().exec(res => {
this.$set(this.ctrl, 'h'+i, res[0].height)
})
})
// #endif
const i = e.currentTarget.dataset.i
/* #ifndef H5 || (APP-PLUS && VUE2) */
if (!this.childs[i].w) {
this.$set(this.ctrl, i, e.detail.width)
if (this.opts[5]) {
const path = this.opts[7] + '.' + i + '.attrs.'
if (e.detail.width < 150)
this.root._setData(path + 'ignore', 'T')
this.root._setData(path + 'width', e.detail.width.toString())
}
} else /* #endif */ if ((this.opts[1] && !this.ctrl[i]) || this.ctrl[i] === -1) {
//
this.$set(this.ctrl, i, 1)
}
},
/**
* @description 链接点击事件
* @param {Event} e
*/
linkTap (e) {
if (!this.opts[5]) {
const node = e.currentTarget ? this.childs[e.currentTarget.dataset.i] : {}
const attrs = node.attrs || e
const href = attrs.href
this.root.$emit('linktap', Object.assign({
innerText: this.root.getText(node.children || []) //
}, attrs))
if (href) {
if (href[0] === '#') {
//
this.root.navigateTo(href.substring(1)).catch(() => { })
} else if (href.split('?')[0].includes('://')) {
//
if (this.root.copyLink) {
// #ifdef H5
window.open(href)
// #endif
// #ifdef MP
uni.setClipboardData({
data: href,
success: () =>
uni.showToast({
title: '链接已复制'
})
})
// #endif
// #ifdef APP-PLUS
plus.runtime.openWeb(href)
// #endif
}
} else {
//
uni.navigateTo({
url: href,
fail () {
uni.switchTab({
url: href,
fail () { }
})
}
})
}
}
} else {
const i = e.currentTarget.dataset.i
const node = this.childs[i]
const items = this.root._getItem(node)
this.root._tooltip({
top: getTop(e),
items,
success: tapIndex => {
if (items[tapIndex] === '更换链接') {
this.root.getSrc('link', node.attrs.href).then(url => {
this.root._editVal(this.opts[7] + '.' + i + '.attrs.href', node.attrs.href, url, true)
uni.showToast({
title: '成功'
})
}).catch(() => { })
} else {
this.remove(i)
}
}
})
}
},
/**
* @description 错误事件
* @param {Event} e
*/
mediaError (e) {
const i = e.currentTarget.dataset.i
const node = this.childs[i]
//
if (node.name === 'video' || node.name === 'audio') {
let index = (this.ctrl[i] || 0) + 1
if (index > node.src.length) {
index = 0
}
if (index < node.src.length) {
this.$set(this.ctrl, i, index)
return
}
} else if (node.name === 'img') {
// #ifdef H5 && VUE3
if (this.opts[0] && !this.ctrl.load) return
// #endif
//
if (this.opts[2]) {
this.$set(this.ctrl, i, -1)
}
}
if (this.root) {
this.root.$emit('error', {
source: node.name,
attrs: node.attrs,
// #ifndef H5 && VUE3
errMsg: e.detail.errMsg
// #endif
})
}
}
}
}
</script>
<style>/* #ifndef H5 || MP-ALIPAY || APP-PLUS */
/deep/ ._address,
/deep/ ._article,
/deep/ ._aside,
/deep/ ._body,
/deep/ ._caption,
/deep/ ._center,
/deep/ ._cite,
/deep/ ._footer,
/deep/ ._header,
/deep/ ._html,
/deep/ ._nav,
/deep/ ._pre,
/deep/ ._section {
display: block;
}
/* #endif */
/deep/ ._video {
width: 300px;
height: 225px;
display: inline-block;
background-color: black;
}
/* a 标签默认效果 */
._a {
padding: 1.5px 0 1.5px 0;
color: #366092;
word-break: break-all;
}
/* a 标签点击态效果 */
._hover {
text-decoration: underline;
opacity: 0.7;
}
/* 图片默认效果 */
._img {
max-width: 100%;
-webkit-touch-callout: none;
}
/* 内部样式 */
._block {
display: block;
}
._b,
._strong {
font-weight: bold;
}
._code {
font-family: monospace;
}
._del {
text-decoration: line-through;
}
._em,
._i {
font-style: italic;
}
._h1 {
font-size: 2em;
}
._h2 {
font-size: 1.5em;
}
._h3 {
font-size: 1.17em;
}
._h5 {
font-size: 0.83em;
}
._h6 {
font-size: 0.67em;
}
._h1,
._h2,
._h3,
._h4,
._h5,
._h6 {
display: block;
font-weight: bold;
}
._image {
height: 1px;
}
._ins {
text-decoration: underline;
}
._li {
display: list-item;
}
._ol {
list-style-type: decimal;
}
._ol,
._ul {
display: block;
padding-left: 40px;
margin: 1em 0;
}
._q::before {
content: '"';
}
._q::after {
content: '"';
}
._sub {
font-size: smaller;
vertical-align: sub;
}
._sup {
font-size: smaller;
vertical-align: super;
}
._thead,
._tbody,
._tfoot {
display: table-row-group;
}
._tr {
display: table-row;
}
._td,
._th {
display: table-cell;
vertical-align: middle;
}
._th {
font-weight: bold;
text-align: center;
}
._ul {
list-style-type: disc;
}
._ul ._ul {
margin: 0;
list-style-type: circle;
}
._ul ._ul ._ul {
list-style-type: square;
}
._abbr,
._b,
._code,
._del,
._em,
._i,
._ins,
._label,
._q,
._span,
._strong,
._sub,
._sup {
display: inline;
}
/* #ifdef APP-PLUS */
._video {
width: 300px;
height: 225px;
}
/* #endif */
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,320 @@
<template>
<view class="rider-swipeout-wrap" :style="{ backgroundColor: backgroundColor }">
<view class="rider-swipeout-item" :class="[isShowBtn ? 'swipe-action-show' : '']" :style="{ transform: 'translate(' + position.pageX + 'px,0)' }">
<view class="rider-swipeout-content" @touchstart="handlerTouchstart" @touchmove="handlerTouchmove" @touchend="handlerTouchend" @mousedown="handlerTouchstart" @mousemove="handlerTouchmove" @mouseup="handlerTouchend">
<slot name="content"></slot>
</view>
<view class="rider-swipeout-button-right-group" v-if="actions.length > 0" @touchend.stop="loop">
<view class="rider-swipeout-button-right-item" v-for="(item, index) in actions" :key="index" :style="{ backgroundColor: item.background || '#f7f7f7', color: item.color, width: item.width + 'px', height: item.width + 'px', }" :data-index="index" @tap="handlerButton">
<!-- <image :src="item.icon" v-if="item.icon" :style="{ width: px(item.imgWidth), height: px(item.imgHeight) }"></image> -->
<u-icon v-if="item.icon" :name="item.icon" :size="item.fontsize"></u-icon>
<text v-else :style="{ fontSize: `${item.fontsize}rpx`}">{{ item.name }}</text>
</view>
</view>
<!--actions长度设置为0可直接传按钮进来-->
<view class="rider-swipeout-button-right-group" @touchend.stop="loop" @tap="handlerParentButton" v-if="actions.length === 0" :style="{ width: operateWidth + 'px', right: '-' + operateWidth + 'px' }">
<slot name="button"></slot>
</view>
</view>
<view v-if="isShowBtn && showMask" class="swipe-action_mask" @tap.stop="closeButtonGroup" @touchstart.stop.prevent="closeButtonGroup"></view>
</view>
</template>
<script>
export default {
name: 'riderSwipeAction',
emits: ['click'],
props: {
// name: '',
// color: '#fff',
// fontsize: 32,//rpx
// width: 80, //px
// icon: 'like.png',//
// background: '#ed3f14'
actions: {
type: Array,
default () {
return [];
}
},
//
closable: {
type: Boolean,
default: true
},
//false
showMask: {
type: Boolean,
default: true
},
operateWidth: {
type: Number,
default: 80
},
params: {
type: Object,
default () {
return {};
}
},
//
forbid: {
type: Boolean,
default: false
},
//
open: {
type: Boolean,
default: false
},
//
backgroundColor: {
type: String,
default: '#fff'
}
},
watch: {
actions(newValue, oldValue) {
this.updateButtonSize();
},
open(newValue) {
this.manualSwitch(newValue);
}
},
data() {
return {
//start position
tStart: {
pageX: 0,
pageY: 0
},
//
limitMove: 0,
//move position
position: {
pageX: 0,
pageY: 0
},
isShowBtn: false,
move: false
};
},
mounted() {
this.updateButtonSize();
},
methods: {
swipeDirection(x1, x2, y1, y2) {
return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : y1 - y2 > 0 ? 'Up' :
'Down';
},
//
loop() {},
updateButtonSize() {
const actions = this.actions;
if (actions.length > 0) {
const query = uni.createSelectorQuery().in(this);
let limitMovePosition = 0;
actions.forEach(item => {
limitMovePosition += item.width || 0;
});
this.limitMove = limitMovePosition;
} else {
this.limitMove = this.operateWidth;
}
},
handlerTouchstart(event) {
if (this.forbid) return;
let touches = event.touches
if (touches && touches.length > 1) return;
this.move = true;
touches = touches ? event.touches[0] : {};
if (!touches || (touches.pageX === undefined && touches.pageY === undefined)) {
touches = {
pageX: event.pageX,
pageY: event.pageY
};
}
const tStart = this.tStart;
if (touches) {
for (let i in tStart) {
if (touches[i]) {
tStart[i] = touches[i];
}
}
}
},
swipper(touches) {
const start = this.tStart;
const spacing = {
pageX: touches.pageX - start.pageX,
pageY: touches.pageY - start.pageY
};
if (this.limitMove < Math.abs(spacing.pageX)) {
spacing.pageX = -this.limitMove;
}
this.position = spacing;
// console.log('===', this.position )
},
handlerTouchmove(event) {
if (this.forbid || !this.move) return;
const start = this.tStart;
let touches = event.touches ? event.touches[0] : {};
if (!touches || (touches.pageX === undefined && touches.pageY === undefined)) {
touches = {
pageX: event.pageX,
pageY: event.pageY
};
}
if (touches) {
const direction = this.swipeDirection(start.pageX, touches.pageX, start.pageY, touches.pageY);
if (direction === 'Left' && Math.abs(this.position.pageX) !== this.limitMove) {
this.swipper(touches);
}
}
},
handlerTouchend(event) {
if (this.forbid || !this.move) return;
this.move = false;
const start = this.tStart;
let touches = event.changedTouches ? event.changedTouches[0] : {};
if (!touches || (touches.pageX === undefined && touches.pageY === undefined)) {
touches = {
pageX: event.pageX,
pageY: event.pageY
};
}
if (touches) {
const direction = this.swipeDirection(start.pageX, touches.pageX, start.pageY, touches.pageY);
const spacing = {
pageX: touches.pageX - start.pageX,
pageY: touches.pageY - start.pageY
};
if (Math.abs(spacing.pageX) >= 40 && direction === 'Left') {
spacing.pageX = spacing.pageX < 0 ? -(this.limitMove + this.actions.length * 8 + 8) : this.limitMove;
this.isShowBtn = true;
} else {
spacing.pageX = 0;
}
if (spacing.pageX == 0) {
this.isShowBtn = false;
}
this.position = spacing;
}
},
handlerButton(event) {
if (this.closable) {
this.closeButtonGroup();
}
const dataset = event.currentTarget.dataset;
this.$emit('click', {
index: Number(dataset.index),
item: this.params
});
},
closeButtonGroup() {
this.position = {
pageX: 0,
pageY: 0
};
this.isShowBtn = false;
},
//
handlerParentButton(event) {
if (this.closable) {
this.closeButtonGroup();
}
},
manualSwitch(isOpen) {
let x = 0;
if (isOpen) {
if (this.actions.length === 0) {
x = this.operateWidth;
} else {
let width = 0;
this.actions.forEach(item => {
width += item.width;
});
x = width;
}
}
this.position = {
pageX: -x,
pageY: 0
};
},
px(num) {
return uni.upx2px(num) + 'px';
}
}
};
</script>
<style scoped>
.rider-swipeout-wrap {
width: 710rpx;
margin: 20rpx;
border-radius: 16rpx;
position: relative;
overflow: hidden;
}
.swipe-action-show {
position: relative;
z-index: 998;
}
.rider-swipeout-item {
width: 100%;
/* padding: 15px 20px; */
box-sizing: border-box;
transition: transform 0.2s ease;
font-size: 14px;
cursor: pointer;
}
.rider-swipeout-content {
white-space: nowrap;
overflow: hidden;
}
.rider-swipeout-button-right-group {
position: absolute;
right: -100%;
top: 0;
height: 100%;
z-index: 1;
width: 100%;
padding: 0 8px;
background: #f6f6f6;
display: flex;
align-items: center;
}
.rider-swipeout-button-right-item+.rider-swipeout-button-right-item {
margin-left: 8px;
}
.rider-swipeout-button-right-item {
border-radius: 50%;
/* float: left; */
white-space: nowrap;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.swipe-action_mask {
display: block;
opacity: 0;
position: fixed;
z-index: 997;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>

View File

@ -0,0 +1,624 @@
<template xlang="wxml">
<view class="rider-tree">
<view class="rider-tree-mask" :class="{ show: showTree }" @tap="_cancel"></view>
<view class="rider-tree-cnt" :class="{ show: showTree }">
<view class="rider-tree-bar">
<view class="rider-tree-bar-cancel" :style="{ color: cancelColor }" hover-class="hover-c" @tap="_cancel">取消</view>
<view class="rider-tree-bar-title" :style="{ color: titleColor }">{{ title }}</view>
<view class="rider-tree-bar-confirm" :style="{ color: confirmColor }" hover-class="hover-c" @tap="_confirm">确定</view>
</view>
<view class="rider-tree-view">
<scroll-view class="rider-tree-view-sc" :scroll-y="true">
<block v-for="(item, index) in treeList" :key="index">
<view
class="rider-tree-item"
:style="[{ paddingLeft: item.rank * 15 + 'px', zIndex: item.rank * -1 + 50 }]"
:class="{ show: item.show, last: item.lastRank, showchild: item.showChild, open: item.open }"
>
<view class="rider-tree-label" @tap.stop="onTreeItemTap(item, index)">
<image
class="rider-tree-icon"
:src="item.lastRank ? lastIcon : item.showChild ? currentIcon : defaultIcon"
></image>
{{ item.name }}
</view>
<view class="rider-tree-check" @tap.stop="onTreeItemSelect(item, index)" v-if="selectParent ? true : item.lastRank">
<view
class="rider-tree-check-yes"
v-if="item.checked"
:class="{ radio: !multiple }"
:style="{ 'border-color': confirmColor }"
>
<view class="rider-tree-check-yes-b" :style="{ 'background-color': confirmColor }"></view>
</view>
<view
class="rider-tree-check-no"
v-else
:class="{ radio: !multiple }"
:style="{ 'border-color': confirmColor }"
></view>
</view>
</view>
</block>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'rider-tree',
props: {
range: {
type: Array,
default: function() {
return []
}
},
idKey: {
//key
type: String,
default: 'id'
},
nameKey: {
//value
type: String,
default: 'name'
},
allKey: {
//
type: String,
default: 'value'
},
title: {
//
type: String,
default: ''
},
multiple: {
//
type: Boolean,
default: true
// default: true
},
cascade: {
//
type: Boolean,
default: false
// default: true
},
selectParent: {
//
type: Boolean,
default: true
},
foldAll: {
//
type: Boolean,
default: false
},
confirmColor: {
//
type: String,
default: '' // #007aff
},
cancelColor: {
//
type: String,
default: '' // #757575
},
titleColor: {
//
type: String,
default: '' // #757575
},
currentIcon: {
// ic
type: String,
default:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFEAAABRCAYAAACqj0o2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MEQ0QTM0MzQ1Q0RBMTFFOUE0MjY4NzI1Njc1RjI1ODIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MEQ0QTM0MzU1Q0RBMTFFOUE0MjY4NzI1Njc1RjI1ODIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowRDRBMzQzMjVDREExMUU5QTQyNjg3MjU2NzVGMjU4MiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowRDRBMzQzMzVDREExMUU5QTQyNjg3MjU2NzVGMjU4MiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PidwepsAAAK0SURBVHja7JxbTsJAFIYHww7ciStgCeoGvGxAiOsgURegoL5720AXYLiIr0aJviq3Zx3PhIEnKG3ndtr+f3KixrSUj/ZjzjClIqUUiFm2gAAQAREQEUAEREAERAQQAREQAREBREAEREBEEqa67h9RFDWllDv0awWYlqlQHmu1WjMRRMoV1QFttA12y3xRtdNczq8EsE4/f8FumX2q77ROvNXk8UGMEKdUz6tYJHljaZAbuyUH+UR1to5BEohTuqwPCeS4pAA/qY6o/kyHOAMCeRK3owJnj+rH1jjxhqpVsstaebCz6TmnHWyXyY+xHjSBWBY/bvSgadtXBj9u9KCN3rnIfkzkQVsTEEX0Y2IP2oKo/HhMICcFAThUcwVZNGU6FdbX/XURzkbVF4+ybGhjPrFdgP66QdXNurGtSdk6Xdb9nAJ8oDo3OQlsQZzkdPw41ONBo6vI5scDefRjZg+6gpg3Pxp50CXEvPjR2IOuIXL3oxUPuobI3Y9WPOgDIlc/WvOgL4iL/vqFCcD7LH0xB4hj7cfQ/fWH9qCT+FhG0tN+DBk1PzjOM0SVllixcsBT1AvYc/kAPhc0hRg/3uvxoCgKRN9+dOrBUBB9+9GpB0NC9OVH5x4MDdG1H714kANEV3705kEOEBf9dcPi/lQnsuvLg1wgSu3Ha0v7Uh4MMgUXeuG71H407a+VBy9CPQkOdw+MtB+nGbd/D+FBbhBNxo9SjwcngJjNj0E9yBFiFj8G9SBXiGn8GNyDnCEm8SMLD3KHGOdHNh7kDjHOj2w8mAeIi/5arX+c6b/fxHz9oADEdGdjR/fXCw/OOB5oVfCOgnepz8IB14PMw03jCmTE+QBx5z0gAmKSqK9OUF+hcAeIhu/QYr4Qie8rjW83hhMBERARQAREQAREBBABERCLnH8BBgA+TQI7U4t53AAAAABJRU5ErkJggg=='
},
defaultIcon: {
// ic
type: String,
default:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFEAAABRCAYAAACqj0o2AAACE0lEQVR4Xu3c200DMRCF4XEltJAOkEugA+ggpUAHoQMqiFMCdEAJUMEiS4mEELlIO7bPOeN9i6K1rG/952myyea1WiCtXmEuYBPR4RBMxInoIOCwhOtJLKVszWyXc/5y2BvNEq6I+/3+kFK6M7OHnPM7jcLKjbZAvD/uaZtzflm5P4rbWyJWgDcze1LPuzVihfxUz7sH4ilJ2bx7Isrm3RtRMu8RiHJ5j0SUyXs0okTeCIj0eSMh0uaNhkiZNyIiXd7IiDR5oyNS5M2ACJ83EyJs3myIkHkzIsLlzYwIkzc7IkTeCojD81ZCHJa3GuKQvBURu+etjNgtb3XELnlHQGyedyTEZnlHQ2ySd0RE97wjI7rlHR3RJe+JeIrbLOecD6ePpZQ6W1kn2epo4MUrPOKyLN8ppYq1+y1VStncOjIdGnFZlo+U0uOtWOeOY2TE12Ouq//pEA7xXL7XfvcufR8K0Svfv6CREN3yDYfYIt9QiK3yjYTYLF95xB75SiP2ylcZsVu+cogj8pVCHJWvEuKwfOkREfKlRkTJlxkRJl86RMR8qRBR82VChM0XHpEhX2hElnyREWnyhUNkzBcKkTVfJETafIcjKuQ7FFEl35GIMvl2R1TMtyuiar49EWXzbY5oZpv/hibXTF2h3+s60FRKeT6+3TjMS3nrA3ZFRD8xrfY3ER1kJ+JEdBBwWGKeRAfEH1wS5WFZSDB/AAAAAElFTkSuQmCC'
},
lastIcon: {
// ic
type: String,
default: ''
}
},
data() {
return {
showTree: false,
treeList: [],
selectIndex: -1,
returnedItem: [], //
pids: [],
ancestorsIds: [],
childNums: []
}
},
computed: {},
methods: {
_show() {
this.showTree = true
},
_hide() {
this.showTree = false
},
_cancel() {
this._hide()
this.$emit('cancel', [])
},
_confirm() {
//
let rt = [],
obj = {}
this.treeList.forEach((v, i) => {
if (this.treeList[i].checked) {
rt.push({
id: this.treeList[i].id,
name: this.treeList[i].name,
value: this.treeList[i].value
})
}
})
this._hide()
this.$emit('confirm', rt)
},
//
_renderTreeList(list = [], rank = 0, parentId = [], parents = []) {
list.forEach(item => {
this.treeList.push({
id: item[this.idKey],
name: item[this.nameKey],
value: item[this.allKey],
source: item,
parentId, // id
parents, // id
rank, //
showChild: false, //
open: false, //
show: rank === 0, //
hideArr: [],
orChecked: item.checked ? item.checked : false,
checked: item.checked ? item.checked : false,
childNum: 0
})
if (Array.isArray(item.children) && item.children.length > 0) {
// console.log(item)
let parentid = [...parentId],
parentArr = [...parents]
delete parentArr.children
parentid.push(item[this.idKey])
parentArr.push({
[this.idKey]: item[this.idKey],
[this.nameKey]: item[this.nameKey],
[this.allKey]: item[this.allKey]
})
this._renderTreeList(item.children, rank + 1, parentid, parentArr)
} else {
this.treeList[this.treeList.length - 1].lastRank = true
}
})
},
//
_defaultSelect() {
this.treeList.forEach((v, i) => {
if (v.checked) {
this.treeList.forEach((v2, i2) => {
if (v.parentId.toString().indexOf(v2.parentId.toString()) >= 0) {
v2.show = true
if (v.parentId.includes(v2.id)) {
v2.showChild = true
v2.open = true
}
}
})
}
})
},
getOwn(id, arr) {
//foreach
arr.forEach(item => {
//
if (item[this.idKey] == id) {
//
this.returnedItem = item
} else if (item.children != null) {
//chlidren
//
this.getOwn(id, item.children)
}
})
return this.returnedItem
},
setShow(id, arr, isShow) {
arr.forEach((item, index) => {
if (item.parentId.includes(id)) {
this.treeList[index].showChild = isShow
this.treeList[index].show = isShow
} else if (item.children !== undefined) {
this.setShow(id, item.children, isShow)
}
})
},
//
onTreeItemTap(item, index) {
// console.log(item)
if (item.lastRank === true) {
//
this.treeList[index].checked = !this.treeList[index].checked
this._fixMultiple(index)
return
}
let id = item.id
item.showChild = !item.showChild
// qingqian
if (item.showChild) {
const range = this.range
const parentIdArr = item.parentId
//
const own = this.getOwn(id, range)
const checkedChildren = own.children
//
const nextIndex = this.treeList.findIndex(itemT => itemT.id === item.id)
if (checkedChildren === undefined || checkedChildren.length < 1) {
return
}
//
this.treeList[index].childNum = checkedChildren.length
const newRank = item.rank + 1
checkedChildren.forEach(itemC => {
const childObj = {
id: itemC[this.idKey],
name: itemC[this.nameKey],
value: itemC[this.allKey],
source: {},
parentId: [item.id], // id
parents: [item], // id
rank: newRank, //
showChild: false, //
open: false, //
show: 1, //
hideArr: [],
lastRank: itemC.children && itemC.children.length > 0 ? false : true,
orChecked: this.treeList[index].checked,
checked: this.cascade ? this.treeList[index].checked : false
}
if (!this.treeList.some(itemT => itemT.id === itemC[this.idKey])) {
this.treeList.splice(nextIndex + 1, 0, childObj)
}
})
}
// //
let list = this.treeList
item.open = item.showChild ? true : !item.open
list.forEach((childItem, i) => {
if (item.showChild === false) {
//
if (!childItem.parentId.includes(id)) {
return
}
//TODO:
if (!this.foldAll) {
if (childItem.lastRank !== true && !childItem.open) {
childItem.showChild = false
this.setShow(childItem.id, this.treeList, false)
}
//
if (childItem.show) {
childItem.hideArr[item.rank] = id
}
} else {
if (childItem.lastRank !== true) {
childItem.showChild = false
//
this.setShow(childItem.id, this.treeList, false)
}
}
if (childItem.children !== undefined) {
childItem.children.forEach((childItem1, i1) => {
if (!childItem1.parentId.includes(childItem.id)) {
return
}
childItem.children[i1].showChild = false
childItem.children[i1].show = false
})
}
childItem.show = false
} else {
//
if (childItem.parentId[childItem.parentId.length - 1] === id) {
childItem.show = true
}
//
if (childItem.parentId.includes(id) && !this.foldAll) {
// console.log(childItem.hideArr)
if (childItem.hideArr[item.rank] === id) {
childItem.show = true
if (childItem.open && childItem.showChild) {
childItem.showChild = true
} else {
childItem.showChild = false
}
childItem.hideArr[item.rank] = null
}
}
}
})
},
// id
syncChecked(trees, pid, checked) {
trees.forEach((item, index) => {
if (item.parentId.includes(pid)) {
this.treeList[index].checked = checked
this.syncChecked(trees, item.id, checked)
} else if (item.children !== undefined) {
this.syncChecked(item.children, pid, checked)
}
})
},
// id
setAncestors(pids, checked) {
this.treeList.forEach((item, index) => {
if (pids.includes(item.id)) {
if (checked && this.childNums[item.id] !== undefined && item.childNum === this.childNums[item.id]) {
// ,
this.treeList[index].checked = true
} else {
this.treeList[index].checked = false
}
this.setAncestors(item.parentId, checked)
}
})
},
onTreeItemSelect(item, index) {
this.treeList[index].checked = !this.treeList[index].checked
// ,
if (this.cascade) {
this.syncChecked(this.treeList, item.id, this.treeList[index].checked)
}
if (item.rank > 0) {
item.parentId.forEach((pid, indexP) => {
const parent = this.treeList.filter(i => i.id === pid)
const childNum = parent.length > 0 ? parent[0].childNum : 0
if (this.childNums[pid] === undefined) {
this.childNums[pid] = 1
} else if (this.childNums[pid] < childNum) {
this.childNums[pid]++
}
})
////,
if (this.cascade) {
this.setAncestors(item.parentId, this.treeList[index].checked)
}
}
this._fixMultiple(index)
},
//
_fixMultiple(index) {
if (!this.multiple) {
//
this.treeList.forEach((v, i) => {
if (i != index) {
this.treeList[i].checked = false
} else {
this.treeList[i].checked = true
}
})
}
},
//
_reTreeList() {
this.treeList.forEach((v, i) => {
this.treeList[i].checked = v.orChecked
})
},
_initTree(range = this.range) {
this.treeList = []
this._renderTreeList(range)
this.$nextTick(() => {
this._defaultSelect(range)
})
}
},
watch: {
range(list) {
this._initTree(list)
},
multiple() {
if (this.range.length) {
this._reTreeList()
}
},
selectParent() {
if (this.range.length) {
this._reTreeList()
}
}
},
mounted() {
this._initTree()
}
}
</script>
<style scoped>
.rider-tree-mask {
position: fixed;
top: 0rpx;
right: 0rpx;
bottom: 0rpx;
left: 0rpx;
z-index: 9998;
background-color: rgba(0, 0, 0, 0.6);
opacity: 0;
transition: all 0.3s ease;
visibility: hidden;
}
.rider-tree-mask.show {
visibility: visible;
opacity: 1;
}
.rider-tree-cnt {
position: fixed;
top: 0rpx;
right: 0rpx;
bottom: 0rpx;
left: 0rpx;
z-index: 19999;
top: 15vh;
transition: all 0.3s ease;
transform: translateY(100%);
}
.rider-tree-cnt.show {
transform: translateY(0);
}
.rider-tree-bar {
background-color: #fff;
height: 72rpx;
padding-left: 20rpx;
padding-right: 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
border-bottom-width: 1rpx !important;
border-bottom-style: solid;
border-bottom-color: #f5f5f5;
font-size: 32rpx;
color: #757575;
line-height: 1;
}
.rider-tree-bar-confirm {
color: #007aff;
}
.rider-tree-view {
position: absolute;
top: 0rpx;
right: 0rpx;
bottom: 0rpx;
left: 0rpx;
top: 72rpx;
background-color: #fff;
padding-top: 20rpx;
padding-right: 20rpx;
padding-bottom: 20rpx;
padding-left: 20rpx;
}
.rider-tree-view-sc {
height: 100%;
overflow: hidden;
}
.rider-tree-item {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 26rpx;
color: #757575;
line-height: 1;
height: 0;
opacity: 0;
transition: 0.2s;
position: relative;
overflow: hidden;
}
.rider-tree-item.show {
height: 80rpx;
opacity: 1;
}
.rider-tree-item.showchild:before {
transform: rotate(90deg);
}
.rider-tree-item.last:before {
opacity: 0;
}
.rider-tree-icon {
width: 26rpx;
height: 26rpx;
margin-right: 8rpx;
}
.rider-tree-label {
flex: 1;
display: flex;
align-items: center;
height: 100%;
line-height: 1.2;
}
.rider-tree-check {
width: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
}
.rider-tree-check-yes,
.rider-tree-check-no {
width: 20px;
height: 20px;
border-top-left-radius: 20%;
border-top-right-radius: 20%;
border-bottom-right-radius: 20%;
border-bottom-left-radius: 20%;
border-top-width: 1rpx;
border-left-width: 1rpx;
border-bottom-width: 1rpx;
border-right-width: 1rpx;
border-style: solid;
border-color: #007aff;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
}
.rider-tree-check-yes-b {
width: 12px;
height: 12px;
border-top-left-radius: 20%;
border-top-right-radius: 20%;
border-bottom-right-radius: 20%;
border-bottom-left-radius: 20%;
background-color: #007aff;
}
.rider-tree-check .radio {
border-top-left-radius: 50%;
border-top-right-radius: 50%;
border-bottom-right-radius: 50%;
border-bottom-left-radius: 50%;
}
.rider-tree-check .radio .rider-tree-check-yes-b {
border-top-left-radius: 50%;
border-top-right-radius: 50%;
border-bottom-right-radius: 50%;
border-bottom-left-radius: 50%;
}
.hover-c {
opacity: 0.6;
}
</style>

View File

@ -0,0 +1,475 @@
<template>
<view>
<!-- head -->
<view class="head-area" :style="{ height: `${headHeight}rpx` }">
<u-navbar
:is-fixed="false"
:border-bottom="false"
:is-back="true"
:custom-back="navBack"
:title="isWeixin ? '' : option.navTitle"
:back-icon-name="option.navBackIcon || 'arrow-leftward'"
back-icon-color="#fff"
back-icon-size="35"
:background="{ background: 'transparent' }"
title-color="#fff"
>
<view class="nav-title" v-if="isWeixin">{{ option.navTitle }}</view>
<view class="nav-right" slot="right" v-if="option.manageBtn" @click="manageSwitch = !manageSwitch">
{{ manageSwitch ? '完成' : '管理' }}
</view>
</u-navbar>
<view class="search-item" v-if="option.searchShow">
<u-search
placeholder="搜索内容"
v-model="searchValue"
@search="onSearch(searchValue)"
@clear="onSearch(searchValue)"
shape="square"
bg-color="#8EAAFF"
placeholder-color="#5470C4"
color="#fff"
search-icon-color="#ffffffE5"
:clearabled="true"
:show-action="false"
></u-search>
</view>
</view>
<!-- head seat -->
<view :style="{ height: `${headHeight}rpx` }"></view>
<!-- list -->
<block v-if="data && data.length > 0">
<view class="" v-for="(item, index) in data" :key="index">
<crud-item
:ref="`crud-item${index}`"
:list="data"
:node="item"
:option="option"
:nodeIndex="index"
:checkList="checkList"
:checkShow="manageSwitch"
:random-num="randomNum"
></crud-item>
</view>
<view style="padding: 20rpx 0;"><slot name="loadmore"></slot></view>
</block>
<!-- foot -->
<block v-if="manageSwitch">
<view :style="[{ height: isFullSucreen ? '168rpx' : '100rpx' }]"></view>
<view class="foot flex" v-if="manageSwitch" :class="[isFullSucreen ? 'foot-ipx' : '']">
<checkbox-group @change="onCheckedAll">
<label class="well-check flex flex-c">
<checkbox
color="#fff"
style="transform: scale(0.7)"
value="allSelect"
:checked="checkedAll"
class="curdCheck"
></checkbox>
<text class="checkAll">全选 ({{ checkList.length }})</text>
</label>
</checkbox-group>
<view class=""><view class="foot-del-btn" @click="onRemove">删除</view></view>
</view>
</block>
<!-- form -->
<u-popup
v-model="createShow"
safe-area-inset-bottom
border-radius="14"
mode="bottom"
closeable
@close="onCreateClose"
:mask-close-able="false"
>
<view class="popup-head"><u-section :title="action" :right="false" line-color="#5f88ff"></u-section></view>
<scroll-view scroll-y="true" style="height: 68vh;">
<view class="form">
<block v-if="formType == 'uviewForm'">
<u-form :model="value" ref="uForm" :label-width="option.labelWidth || 160"><slot name="form"></slot></u-form>
</block>
<block v-else><slot name="form"></slot></block>
</view>
</scroll-view>
<view class="form-btn">
<u-button
v-if="action != '查看'"
@click="onSubmit"
:loading="disabled"
shape="circle"
ripple
:custom-style="{ width: '100%', background: 'linear-gradient(90deg, #69b0ff, #5f88ff)', color: '#fff' }"
>
保存
</u-button>
</view>
</u-popup>
<!-- addBtn -->
<movable-area class="movable-area">
<movable-view class="movable-view" :x="moveX" :y="moveY" direction="all">
<image src="@/static/images/crud/create.png" @click="onCreateOpen"></image>
</movable-view>
</movable-area>
<!-- empty -->
<slot name="empty">
<block v-if="data.length == 0">
<u-empty text="工作再忙,也要记得喝水~" margin-top="200" :src="require('@/static/images/crud/empty.png')"></u-empty>
</block>
</slot>
</view>
</template>
<script>
import crudItem from './components/crud-item/index'
import { checkModel } from './util/tool.js'
import validate from './util/validate.js'
export default {
name: 'rider-crud',
components: { crudItem },
props: {
value: {}, //
data: Array, //
//
option: {
type: Object,
default: {
column: []
}
},
//
formType: {
type: String,
default: 'uviewForm'
},
//
navBack: {
type: Function,
default: null
},
rules: {}, //
readonly: {
type: Boolean,
default: false
}
},
data() {
return {
isFullSucreen: checkModel(),
moveX: 320, //
moveY: 580,
checkedAll: false, //
checkList: [], //
createShow: false, //form
disabled: false, //
action: '新增',
searchValue: '',
isWeixin: false,
manageSwitch: false,
headHeight: 0,
randomNum: Math.round(Math.random() * 99999) // uni.on
}
},
watch: {
manageSwitch: {
handler(newVal, oldVal) {
this.checkList = []
this.checkedAll = false
},
deep: true,
immediate: true
},
'option.searchShow': {
handler(newVal, oldVal) {
const that = this
this.headHeight = newVal ? 192 : 98
// #ifndef H5
uni.getSystemInfo({
success: function(res) {
const statusBarHeight = Number(res.statusBarHeight) + Number(res.platform == 'ios' ? 44 : 48)
that.headHeight = newVal ? statusBarHeight + 192 : statusBarHeight + 98
}
})
// #endif
},
deep: true,
immediate: true
}
},
//
created() {
let that = this
uni.$on(`action${this.randomNum}`, ({ index }, i, list) => {
that.onActionClick({ index }, i, list)
})
uni.$on(`checked${this.randomNum}`, (e, id) => {
that.onChecked(e, id)
})
// #ifdef MP-WEIXIN
this.isWeixin = true
// #endif
// #ifndef MP-WEIXIN
this.isWeixin = false
// #endif
},
methods: {
setLazyLoad(func) {
this.option.lazyLoad = func
setTimeout(() => {
this.data.forEach((d, i) => {
this.$refs[`crud-item${i}`][0].setLazyLoad(func)
})
}, 100)
},
//
onChecked(e, id) {
const i = e.detail.value[0]
if (i) this.checkList.push(i)
else this.checkList.splice(this.checkList.findIndex(e => e === id), 1)
this.checkedAll = this.checkList.length === this.data.length
},
//
onCheckedAll() {
if (!this.checkedAll) {
this.checkedAll = true
this.checkList = []
this.data.map(e => {
this.checkList.push(e.id)
})
} else {
this.checkedAll = false
this.checkList = []
}
},
//
onClick(item, index) {
const action = { name: '查看' }
const data = JSON.parse(JSON.stringify(item))
this.$emit('input', data)
this.$emit('action-click', { index, data, actionIndex: -1, action, done: this.show })
this.action = action.name
},
//
onRemove() {
if (this.checkList.length === 0) {
uni.showToast({
title: '请选择至少一条数据',
icon: 'none',
duration: 2000
})
return
}
this.$emit('del', this.checkList.join(','))
},
//
onCreateOpen() {
this.action = '新增'
this.$emit('action-click', { action: { name: this.action }, done: this.show })
},
//
onCreateClose() {
this.hide()
},
//
onSubmit() {
this.disabled = true
const data = JSON.parse(JSON.stringify(this.value))
if (this.formType == 'uviewForm') {
// #ifdef MP
validate(this.value, this.rules)
.then(() => {
this.$emit('submit', data, this.action, this.unloading, this.hide)
})
.catch(() => {
this.unloading()
})
// #endif
// #ifndef MP
this.$refs.uForm.validate(valid => {
if (valid) {
this.$emit('submit', data, this.action, this.unloading, this.hide)
} else {
this.unloading()
}
})
// #endif
} else {
validate(this.value, this.rules)
.then(() => {
debugger
this.$emit('submit', data, this.action, this.unloading, this.hide)
})
.catch(() => {
this.unloading()
})
}
},
//
onActionClick({ index }, i, list) {
let action
if (index == -1) action = { name: '查看' }
else action = this.option.actions[index]
const data = JSON.parse(JSON.stringify(list[i]))
if (['新增', '编辑', '查看'].includes(action.name)) this.$emit('input', data)
this.$emit('action-click', { index: i, data, actionIndex: index, action, done: this.show })
// this.action = action.name
this.$set(this, 'action', action.name)
},
//
allCheckHide(val) {
if (this.checkedAll) this.manageSwitch = val
this.checkList = []
},
//
onSearch(val) {
this.$emit('search', val)
},
show() {
this.createShow = true
setTimeout(() => {
if (this.rules && this.$refs['uForm'] && typeof this.$refs['uForm'].setRules == 'function') {
this.$refs['uForm'].setRules(this.rules)
}
}, 100)
},
hide() {
this.createShow = false
this.disabled = false
this.$emit('input', {})
this.$emit('update:readonly', false)
},
unloading() {
this.disabled = false
},
hideChildren() {
this.data.forEach((d, i) => {
this.$refs[`crud-item${i}`][0].hide()
})
}
}
}
</script>
<style lang="scss" scoped>
.flex {
display: flex;
align-items: center;
justify-content: space-between;
}
.flex-one {
flex: 1;
}
.head-area {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
width: 100%;
background: url('/static/images/head_bg.png') no-repeat center bottom;
background-size: 100% 100%;
.nav-right {
color: #fff;
font-size: 30rpx;
font-weight: normal;
padding-right: 30rpx;
}
.nav-title {
color: #fff;
font-size: 32rpx;
font-weight: normal;
}
.search-item {
padding: 0 30rpx;
}
}
.head-h {
height: 200rpx;
}
/* 底部 */
/* 适配 */
.foot-ipx {
bottom: 68rpx !important;
}
.foot-ipx::after {
content: ' ';
position: fixed;
bottom: 0 !important;
left: 0;
right: 0;
height: 68rpx !important;
width: 100%;
background: #fff;
}
.foot {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 99;
width: 100%;
padding: 0 26rpx;
height: 100rpx;
background: #fff;
border-top: 2px solid #eee;
&-del-btn {
width: 160rpx;
height: 64rpx;
line-height: 64rpx;
font-size: 26rpx;
// color: #fff;
text-align: center;
border-radius: 34px;
// background: linear-gradient(180deg, #5f88ff, #69b0ff);
color: #f56c6c;
background: #fef0f0;
}
}
.form {
padding: 0 30rpx 20rpx;
}
.popup-head {
padding: 36rpx 30rpx;
border-bottom: 2rpx dashed #efefef;
}
.form-btn {
margin: 30rpx;
min-height: 80rpx;
}
.movable-area {
height: 100vh;
width: 100%;
top: 0;
position: fixed;
pointer-events: none;
.movable-view {
width: 96rpx;
height: 96rpx;
pointer-events: auto;
image {
width: 96rpx;
height: 96rpx;
}
}
}
</style>

View File

@ -0,0 +1,22 @@
// 判断是否全面屏
export function checkModel(isFlag) {
uni.getSystemInfo({
success: res => {
let model = res.model;
for (var i = 11; i < 30; i++) {
isFlag = (model.indexOf("iPhone X") > -1 || model.indexOf("iPhone " + i) > -1) ? true : false
}
}
})
return isFlag
}
// 获取字典文本
export function getDicLabel(id, list, props = { label: 'dictValue', value: 'dictKey' }) {
if (!list) return {}
for (let i = 0; i < list.length; i++) {
const item = list[i]
if (item[props.value] == id) return item[props.label]
if (item.children) return getDicLabel(id, item.children, props)
}
}

View File

@ -0,0 +1,47 @@
export default (form, rules) => {
return new Promise((resolve, reject) => {
if (form && rules) {
let pass = true
a: for (let key in rules) {
let rule = rules[key]
if (Array.isArray(rule)) {
for (let i = 0; i < rule.length; i++) {
const result = validate(rule[i], form[key])
if (!result) {
pass = false
break a
}
}
} else {
const result = validate(rule, form[key])
if (!result) {
pass = false
break a
}
}
}
if (pass) resolve()
else reject()
} else {
reject('需校验的对象为空')
}
})
}
const validate = (rule, value) => {
let pass = true
if (rule && rule.pattern && !rule.pattern.test(value)) {
uni.showToast({
title: rule.message || '校验失败',
icon: 'none'
})
pass = false
} else if (rule && rule.required && !value) {
uni.showToast({
title: rule.message || '校验失败',
icon: 'none'
})
pass = false
}
return pass
}

4
deploy.sh Normal file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
scp -r ./unpackage/dist/build/h5/* root@127.0.0.1:/data/wwwroot/rider.bladex.vip

83
http/api.js Normal file
View File

@ -0,0 +1,83 @@
import {
clientId,
clientSecret
} from '@/common/setting'
import {
options
} from '@/http/config.js';
import {
Base64
} from '@/utils/base64.js';
import Request from '@/utils/luch-request/index.js';
const http = new Request(options);
http.interceptors.request.use((config) => { // 可使用async await 做异步操作
// 假设有token值需要在头部需要携带
let accessToken = uni.getStorageSync('accessToken');
if (accessToken) {
config.header['Blade-Auth'] = 'bearer ' + accessToken;
}
// 安全请求header
config.header['Blade-Requested-With'] = 'BladeHttpRequest';
// 客户端认证参数
config.header['Authorization'] = 'Basic ' + Base64.encode(clientId + ':' + clientSecret);
// #ifndef H5
let url = config.url
if (process.env.NODE_ENV == 'development' && !url.startsWith("http")) {
url = url.substring(url.indexOf('/api') + 4)
config.url = url
}
// #endif
// 额外参数
// config.data = config.data || {};
// config.data.pf = uni.getSystemInfoSync().platform;
// config.data.sys = uni.getSystemInfoSync().system;
// 演示custom 用处
// if (config.custom.auth) {
// config.header.token = 'token'
// }
// if (config.custom.loading) {
// uni.showLoading()
// }
/**
/*
if (!token) { // 如果token不存在return Promise.reject(config) 会取消本次请求
return Promise.reject(config)
}
**/
return config
}, config => { // 可使用async await 做异步操作
return Promise.reject(config)
})
http.interceptors.response.use((response) => {
// 若有数据返回则通过
if (response.data.access_token || response.data.key) {
return response.data
}
// 服务端返回的状态码不等于200则reject()
if (response.data.code !== 200) {
uni.showToast({
title: response.data.msg,
icon: 'none'
});
return Promise.reject(response);
}
return response.data;
}, (response) => {
/* 对响应错误做点什么 statusCode !== 200*/
uni.showToast({
title: response.data.msg,
icon: 'none'
});
if (response.statusCode == 401) {
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
uni.redirectTo({
url: `/pages/login/login-account?redirect=/${currentPage.route}`
})
}
return Promise.reject(response)
})
export default http;

44
http/config.js Normal file
View File

@ -0,0 +1,44 @@
import {
devUrl,
prodUrl,
contentType
} from '@/common/setting'
var options = {
baseURL: process.env.NODE_ENV === 'development' ? devUrl : prodUrl,
header: {
'Content-Type': contentType
},
method: 'POST',
dataType: 'json',
// #ifndef MP-ALIPAY || APP-PLUS
responseType: 'text',
// #endif
// 注如果局部custom与全局custom有同名属性则后面的属性会覆盖前面的属性相当于Object.assign(全局,局部)
custom: {}, // 全局自定义参数默认值
// #ifdef MP-ALIPAY || MP-WEIXIN
timeout: 30000,
// #endif
// #ifdef APP-PLUS
sslVerify: true,
// #endif
// #ifdef H5
// 跨域请求时是否携带凭证cookies仅H5支持HBuilderX 2.6.15+
//withCredentials: false,
// #endif
// #ifdef APP-PLUS
firstIpv4: false, // DNS解析时优先使用ipv4 仅 App-Android 支持 (HBuilderX 2.8.0+)
// #endif
// 局部优先级高于全局返回当前请求的task,options。请勿在此处修改options。非必填
// getTask: (task, options) => {
// 相当于设置了请求超时时间500ms
// setTimeout(() => {
// task.abort()
// }, 500)
// },
// 全局自定义验证器。参数为statusCode 且必存在,不用判断空情况。
// validateStatus: (statusCode) => { // statusCode 必存在。此处示例为全局默认配置
// return statusCode >= 200 && statusCode < 300
// }
};
export { options };

18
http/install.js Normal file
View File

@ -0,0 +1,18 @@
// 获取api目录所有js文件
const files = require.context('@/api', false, /\.js$/)
// 此处第二个参数vm就是我们在页面使用的this可以通过vm获取vuex等操作
const install = (Vue, vm) => {
// 将各个定义的接口名称统一放进对象挂载到vm.$u.api下(因为vm就是this也即this.$u.api)
// 自动将所有api挂载到vm.$u.api中
vm.$u.api = {}
files.keys().forEach(key => {
const api = files(key).default
for (let item in api) {
vm.$u.api[item] = api[item]
}
})
}
export default {
install
}

34
main.js Normal file
View File

@ -0,0 +1,34 @@
import Vue from 'vue'
import App from './App'
import store from '@/store';
Vue.config.productionTip = false;
App.mpType = 'app';
// 引入全局uView
import uView from 'uview-ui'
Vue.use(uView);
// 引入vuex
const vuexStore = require("@/store/$u.mixin.js");
Vue.mixin(vuexStore);
// 创建对象
const app = new Vue({
store,
...App
});
// 接口集中管理
import httpInstall from '@/http/install.js'
Vue.use(httpInstall, app)
import http from '@/http/api.js'
Vue.prototype.$http = http
// 公共函数
import globalFunc from '@/utils/func.js'
Vue.use(globalFunc, app);
app.$mount()

155
manifest.json Normal file
View File

@ -0,0 +1,155 @@
{
"name": "住房保障预约",
"appid": "__UNI__8FD82FF",
"description": "",
"versionName": "2.0.0",
"versionCode": "100",
"transformPx": false,
/* 5+App */
"app-plus": {
"safearea": {
"bottom": {
"offset": "none"
}
},
"usingComponents": true,
"nvueCompiler": "uni-app",
"compilerVersion": 3,
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
},
/* */
"modules": {},
/* */
"distribute": {
/* android */
"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_FINE_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.CALL_PHONE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<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.GET_ACCOUNTS\"/>",
"<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.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios */
"ios": {},
/* SDK */
"sdkConfigs": {
"ad": {}
}
},
"uniStatistics": {
"enable": false
}
},
/* */
"quickapp": {},
/* */
"mp-weixin": {
"appid": "wxa79f37f7f9375f47",
"setting": {
"urlCheck": false,
"minified": true,
"postcss": true,
"es6": true
},
"usingComponents": true,
"uniStatistics": {
"enable": false
}
},
"mp-alipay": {
"usingComponents": true,
"uniStatistics": {
"enable": false
}
},
"mp-baidu": {
"usingComponents": true,
"uniStatistics": {
"enable": false
}
},
"mp-toutiao": {
"usingComponents": true,
"setting": {
"es6": true,
"minified": true,
"postcss": true,
"urlCheck": false
},
"uniStatistics": {
"enable": false
}
},
"h5": {
"template": "template.h5.html",
"publicPath": "/housingAssuranceBookingMobile/",
"router": {
"mode": "hash",
"base": "/housingAssuranceBookingMobile"
},
"optimization": {
"treeShaking": {
"enable": true
}
},
"devServer": {
"proxy": {
"/api": {
"target": "http://192.168.3.111:8181",
// "target" : "http://192.168.3.100:2891",
//"target" : "https://rider.bladex.vip/api",
"pathRewrite": {
"^/api": "/"
}
},
"/epoint-web-cs": {
"target": "https://gxqzzfbzzx.cn",
"pathRewrite": {
"^/epoint-web-cs": "/"
}
}
},
"port": ""
},
"uniStatistics": {
"enable": false
},
"sdkConfigs": {
"maps": {
"qqmap": {
"key": ""
}
}
}
},
"uniStatistics": {
"enable": false
},
"mp-qq": {
"uniStatistics": {
"enable": false
}
}
}

19
package.json Normal file
View File

@ -0,0 +1,19 @@
{
"id": "ba-tree-picker",
"displayName": "ba-tree-picker树形层级选择器支持单选、多选、父级选择、映射",
"version": "1.2",
"description": "树形层级选择器,支持单选、多选、父级选择 、Object对象属性自定义映射、自定义样式等采用弹窗形式样式和比例参照uniapp官方的picker和uni-data-picker组件",
"keywords": [
"选择器",
"树形",
"层级",
"多选",
"ba-tree-picker"
],
"dcloudext": {
"category": [
"前端组件",
"通用组件"
]
}
}

99
pages.json Normal file
View File

@ -0,0 +1,99 @@
{
"easycom": {
"^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue",
"^wf-(.*)": "@/components/wf-ui/components/wf-$1/wf-$1.vue"
},
"pages": [ //pageshttps://uniapp.dcloud.io/collocation/pages
{
"path": "pages/loading/index",
"style": {
"navigationBarTitleText": "云预约",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/home/home",
"style": {
"navigationBarTitleText": "云预约",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/reserve/index",
"style": {
"navigationBarTitleText": "网上预约",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/reserve/notice",
"style": {
"navigationBarTitleText": "预约须知",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/reserve/timeSelect",
"style": {
"navigationBarTitleText": "预约时间",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/reserve/confirm",
"style": {
"navigationBarTitleText": "预约确认",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/myReserve/index",
"style": {
"navigationBarTitleText": "我的预约",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/myReserve/detail",
"style": {
"navigationBarTitleText": "预约详情",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/login/login-phone",
"style": {
"navigationBarTitleText": "手机登录",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
}, {
"path": "pages/login/login-account",
"style": {
"navigationBarTitleText": "账号登录",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#fff",
"backgroundColor": "#F7F7F7"
}
}

24
pages/demo/crud.vue Normal file
View File

@ -0,0 +1,24 @@
<template>
<view class="container">
<u-navbar :is-fixed="false" :border-bottom="false" :is-back="true" back-icon-name="arrow-leftward"
back-icon-color="#fff" back-icon-size="35" :background="{ background: 'linear-gradient(90deg, #69b0ff, #5f88ff)' }" title="CRUD 操作"
title-color="#fff">
</u-navbar>
<view class="u-demo">
<view class="u-demo-wrap">
<view class="u-demo-title">很快到来</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
data: {}
};
},
}
</script>

441
pages/demo/crud1.vue Normal file
View File

@ -0,0 +1,441 @@
<template>
<view class="container">
<view class="safe-area-inset-bottom">
<rider-curd
v-model="form"
:option="option"
:data="list"
:rules="rules"
:readonly.sync="readonly"
formType="uniForm"
@search="onSearch"
@action-click="onActionClick"
@submit="onSubmit"
@del="onRemove"
ref="crud"
>
<template #form>
<view class="uni-form">
<view class="uni-form-item">
<view class="uni-form-item-label uni-form-item-required">通知标题</view>
<view class="uni-form-item-input">
<input
class="uni-input"
v-model="form.title"
placeholder="通知标题"
:placeholder-style="placeholderStyle"
:disabled="readonly"
/>
</view>
</view>
<view class="uni-form-item">
<view class="uni-form-item-label uni-form-item-required">通知类型</view>
<view class="uni-form-item-input">
<picker
@change="e => onConfirm(e, 'category')"
:range="categoryList"
range-key="dictValue"
class="curdPicker"
:disabled="readonly"
>
<input
class="uni-input"
v-model="formLabel.category"
disabled
placeholder="通知类型"
:placeholder-style="placeholderStyle"
/>
</picker>
</view>
<view class="uni-form-item-icon"><u-icon name="arrow-right" color="#999" size="28"></u-icon></view>
</view>
<view class="uni-form-item">
<view class="uni-form-item-label uni-form-item-required">通知日期</view>
<view class="uni-form-item-input">
<picker
@change="e => onConfirm(e, 'releaseTime')"
mode="date"
:value="form.releaseTime"
:start="startDate"
:end="endDate"
class="curdPicker"
:disabled="readonly"
>
<input
class="uni-input"
v-model="form.releaseTime"
disabled
placeholder="通知日期"
:placeholder-style="placeholderStyle"
/>
</picker>
</view>
<view class="uni-form-item-icon"><u-icon name="arrow-right" color="#999" size="28"></u-icon></view>
</view>
<view class="uni-form-item">
<view class="uni-form-item-label">通知内容</view>
<view v-if="!readonly" class="uni-form-item-btn" @click="onRichEdit(form.content)">去编辑</view>
</view>
<mp-html :content="form.content"></mp-html>
</view>
</template>
<template #loadmore>
<u-loadmore :status="loadStatus" @loadmore="getList()" />
</template>
</rider-curd>
</view>
</view>
</template>
<script>
import riderCurd from '@/components/rider-crud/index'
import mpHtml from '@/components/rider-crud/components/mp-html/mp-html'
import { getList, add, update, remove } from '@/api/notice.js'
import { dictionary } from '@/api/system/dict.js'
import { logo } from '../../common/setting'
import { getDicLabel } from '@/components/rider-crud/util/tool.js'
export default {
components: { riderCurd, mpHtml },
data() {
return {
form: {
content: ''
},
formLabel: {
category: '' ,
},
list: [],
params: {
current: 1,
size: 10
},
readonly: false, //
labelWidth: 160,
placeholderStyle: 'color:rgb(192, 196, 204); font-size:14px',
option: {
navTitle: 'CRUD 操作', //
manageBtn: true, //
searchShow: true, //
column: [
{
label: '通知标题',
prop: 'title'
},
{
label: '通知类型',
prop: 'categoryName'
},
{
label: '通知日期',
prop: 'releaseTime'
}
],
actions: [
{
name: '编辑', //
icon: 'edit-pen', //
color: '#333', //
fontsize: 30, //rpx
width: 40, // px
background: '#fff' //
},
{
name: '删除',
icon: 'trash',
color: '#333',
fontsize: 30,
width: 40,
background: '#fff'
}
]
},
rules: {
title: [{ required: true, message: '请输入通知标题', trigger: 'blur' }],
category: [{ required: true, message: '请选择通知类型', trigger: 'change', type: 'number' }],
releaseTime: [{ required: true, message: '请选择通知日期', trigger: 'change' }]
},
loadStatus: 'loadmore', //
checkedSwitch: false,
timeShow: false,
timeParams: {
year: true,
month: true,
day: true,
hour: true,
minute: true,
second: true
},
categoryShow: false,
categoryList: [], //
category: '',
pagesTotal: 0
}
},
computed: {
startDate() {
return this.getDate('start')
},
endDate() {
return this.getDate('end')
}
},
onShow() {
let that = this
uni.$on('richBack', function(data) {
that.$set(that.form, 'content', data)
})
},
onReady() {
this.getList(true)
this.getCategoryList()
},
onPullDownRefresh() {
this.getList(true)
},
onReachBottom() {
if (this.loadStatus == 'nomore') return
this.getList()
},
methods: {
getDate(type) {
const date = new Date()
let year = date.getFullYear()
let month = date.getMonth() + 1
let day = date.getDate()
if (type === 'start') {
year = year - 60
} else {
year = year + 2
}
month = month > 9 ? month : '0' + month
day = day > 9 ? day : '0' + day
return `${year}-${month}-${day}`
},
//
getList(override = false) {
if (override) {
this.params.current = 1
}
const { size } = this.params
getList(this.params)
.then(res => {
const { records, current } = res.data
if (records.length < size) this.loadStatus = 'nomore'
else this.loadStatus = 'loadmore'
if (override) {
this.list = records
} else {
this.list = this.list.concat(records)
}
this.params.current++
uni.stopPullDownRefresh()
})
.catch(err => {
uni.stopPullDownRefresh()
})
},
//
getCategoryList() {
dictionary({ code: 'notice' }).then(res => {
this.categoryList = res.data
})
},
// picker
onConfirm(e, type) {
if (type == 'category') {
const i = this.categoryList[e.detail.value]
this.$set(this.formLabel, type, i.dictValue)
this.$set(this.form, type, Number(i.dictKey))
} else {
this.$set(this.form, type, `${e.detail.value} 12:00:00`)
}
},
//
onRichEdit(e) {
uni.navigateTo({
url: `/pages/rich/index?data=${e}`
})
},
// ()
onSearch(val) {
this.params = {
current: 1,
size: 10,
title: val
}
this.getList(true)
},
//
onActionClick({ index, data, action, done }) {
const { name } = action
switch (name) {
case '新增':
this.formLabel = { category: '' }
done()
break
case '删除':
this.onRemove(data.id)
break
case '查看':
this.readonly = true
case '编辑':
this.formLabel.category = getDicLabel(data.category, this.categoryList)
done()
break
}
},
//
onRemove(id) {
uni.showModal({
title: '确定要删除吗?',
success: action => {
if (action.confirm) {
remove(id).then(() => {
uni.showToast({
title: '删除成功',
icon: 'none'
})
setTimeout(() => {
this.$refs['crud'].allCheckHide(false)
}, 100)
// #ifdef APP
this.getList(true)
// #endif
// #ifndef APP
uni.startPullDownRefresh({})
// #endif
})
}
}
})
},
//
onSubmit(form, action, unloading, done) {
if (action == '新增') {
add(form)
.then(res => {
uni.showToast({
title: '新增成功',
icon: 'none'
})
this.formLabel = { category: '' }
done()
uni.startPullDownRefresh({})
})
.catch(() => {
unloading()
})
} else if (action == '编辑') {
update(form)
.then(res => {
uni.showToast({
title: '编辑成功',
icon: 'none'
})
this.formLabel = { category: '' }
done()
uni.startPullDownRefresh({})
})
.catch(() => {
unloading()
})
}
}
}
}
</script>
<style>
page {
background: #f6f6f6;
}
.uni-picker-container {
z-index: 99999 !important;
}
</style>
<style lang="scss" scoped>
.nav-right {
color: #fff;
font-size: 28rpx;
font-weight: normal;
padding-right: 24rpx;
}
.uni-form {
&-item {
position: relative;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
font-size: 14px;
color: #303133;
line-height: 70rpx;
box-sizing: border-box;
&-label {
width: 160rpx;
.required {
color: #fa3534;
vertical-align: middle;
}
}
&-required {
position: relative;
&:after {
content: '*';
position: absolute;
left: -8px;
color: #fa3534;
vertical-align: middle;
}
}
&-input {
flex: 1;
.uni-input {
font-size: 14px;
color: #303133;
}
}
&-btn {
color: #fff;
width: auto;
font-size: 22rpx;
line-height: 50rpx;
padding: 0 20rpx;
border-radius: 6rpx;
background: linear-gradient(rgb(95, 136, 255), rgb(105, 176, 255));
}
}
&-item:after {
content: ' ';
border-bottom-width: 2rpx;
position: absolute;
left: 0;
top: 0;
pointer-events: none;
box-sizing: border-box;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
width: 199.8%;
height: 199.7%;
-webkit-transform: scale(0.5, 0.5);
transform: scale(0.5, 0.5);
border: 0 solid #e4e7ed;
z-index: 2;
}
&-item:after {
border-bottom-width: 2rpx;
}
}
</style>

326
pages/demo/crud2.vue Normal file
View File

@ -0,0 +1,326 @@
<template>
<view class="container">
<view class="safe-area-inset-bottom">
<rider-curd
v-model="form"
:option="option"
:data="list"
:rules="rules"
:readonly.sync="readonly"
@search="onSearch"
@action-click="onActionClick"
@submit="onSubmit"
@del="onRemove"
ref="crud"
>
<view slot="form">
<u-form-item label="通知标题" prop="title" :label-width="option.labelWidth || 160" required>
<u-input v-model="form.title" :disabled="readonly" placeholder="通知标题" />
</u-form-item>
<u-form-item label="通知类型" prop="category" :label-width="option.labelWidth || 160" required>
<u-input
v-model="formLabel.category"
:disabled="readonly"
placeholder="通知类型"
type="select"
@click="
() => {
if (!readonly) categoryShow = true
}
"
/>
</u-form-item>
<u-form-item label="通知日期" prop="releaseTime" :label-width="option.labelWidth || 160" required>
<u-input
v-model="form.releaseTime"
:disabled="readonly"
placeholder="通知日期"
type="select"
@click="
() => {
if (!readonly) timeShow = true
}
"
/>
</u-form-item>
<u-form-item label="通知内容" :label-width="option.labelWidth || 160" :border-bottom="false">
<template #right v-if="!readonly">
<u-button
@click="onRichEdit(form.content)"
ripple
:custom-style="{ background: '#FFF6E9', color: '#FAA646' }"
size="mini"
>
去编辑
</u-button>
</template>
</u-form-item>
<mp-html :content="form.content"></mp-html>
</view>
<template #loadmore>
<u-loadmore :status="loadStatus" @loadmore="getList()" />
</template>
</rider-curd>
</view>
<!-- 通知类型 -->
<u-select
v-model="categoryShow"
:list="categoryList"
value-name="dictKey"
label-name="dictValue"
@confirm="e => onConfirm(e, 'category')"
safe-area-inset-bottom
></u-select>
<!-- 通知日期 -->
<u-picker
mode="time"
v-model="timeShow"
:params="timeParams"
start-year="2000"
end-year="2050"
@confirm="e => onConfirm(e, 'releaseTime')"
safe-area-inset-bottom
></u-picker>
</view>
</template>
<script>
import riderCurd from '@/components/rider-crud/index'
import mpHtml from '@/components/rider-crud/components/mp-html/mp-html'
import { getList, add, update, remove } from '@/api/notice.js'
import { dictionary } from '@/api/system/dict.js'
import { logo } from '../../common/setting'
import { getDicLabel } from '@/components/rider-crud/util/tool.js'
export default {
components: { riderCurd, mpHtml },
data() {
return {
loadStatus: 'loadmore', //
checkedSwitch: false, //
form: { content: '' }, //
formLabel: {}, // label
list: [], //
params: {
//
current: 1,
size: 10
},
readonly: false, //
option: {
//
navTitle: '通知公告', //
manageBtn: true, //
searchShow: true, //
searchShow: true, //
labelWidth: 160, //formlable 160rpx
column: [
{
label: '通知标题',
prop: 'title'
},
{
label: '通知类型',
prop: 'categoryName'
},
{
label: '通知日期',
prop: 'releaseTime'
}
],
actions: [
//
{
name: '编辑', //
icon: 'edit-pen', //
color: '#333', //
fontsize: 30, //rpx
width: 40, // px
background: '#fff' //
},
{
name: '删除',
icon: 'trash',
color: '#333',
fontsize: 30,
width: 40,
background: '#fff'
}
]
},
rules: {
//
title: [{ required: true, message: '请输入通知标题', trigger: 'blur' }],
category: [{ required: true, message: '请选择通知类型', trigger: 'change', type: 'number' }],
releaseTime: [{ required: true, message: '请选择通知日期', trigger: 'change' }]
},
timeShow: false,
timeParams: {
year: true,
month: true,
day: true,
hour: true,
minute: true,
second: true
},
categoryShow: false,
categoryList: [] //
}
},
onShow() {
let that = this
uni.$on('richBack', function(data) {
that.$set(that.form, 'content', data)
})
},
onReady() {
this.getList(true)
this.getCategoryList()
},
onPullDownRefresh() {
this.getList(true)
},
onReachBottom() {
if (this.loadStatus == 'nomore') return
this.getList()
},
methods: {
//
getList(override = false) {
if (override) {
this.params.current = 1
}
const { size } = this.params
getList(this.params)
.then(res => {
const { records, current } = res.data
if (records.length < size) this.loadStatus = 'nomore'
else this.loadStatus = 'loadmore'
if (override) {
this.list = records
} else {
this.list = this.list.concat(records)
}
this.params.current++
uni.stopPullDownRefresh()
})
.catch(err => {
uni.stopPullDownRefresh()
})
},
//
getCategoryList() {
dictionary({ code: 'notice' }).then(res => {
this.categoryList = res.data
})
},
//
onConfirm(e, type) {
if (type == 'category') {
this.formLabel[type] = e[0].label
this.form.category = Number(e[0].value)
} else {
this.$set(this.form, 'releaseTime', e.year + '-' + e.month + '-' + e.day + ' ' + e.hour + ':' + e.minute + ':' + e.second)
}
},
//
onRichEdit(e) {
uni.navigateTo({
url: `/pages/rich/index?data=${e}`
})
},
// ()
onSearch(val) {
this.params = {
current: 1,
size: 10,
title: val
}
this.getList(true)
},
//
onActionClick({ index, data, action, done }) {
const { name } = action
switch (name) {
case '新增':
this.formLabel = {}
this.form = { content: '' }
done()
break
case '删除':
this.onRemove(data.id)
break
case '查看':
this.readonly = true
case '编辑':
this.formLabel.category = getDicLabel(data.category, this.categoryList)
done()
break
}
},
//
onRemove(id) {
uni.showModal({
title: '确定要删除吗?',
success: action => {
if (action.confirm) {
remove(id).then(() => {
uni.showToast({
title: '删除成功',
icon: 'none'
})
setTimeout(() => {
this.$refs['crud'].allCheckHide(false)
}, 100)
// #ifdef APP
this.getList(true)
// #endif
// #ifndef APP
uni.startPullDownRefresh({})
// #endif
})
}
}
})
},
//
onSubmit(form, action, unloading, done) {
if (action == '新增') {
add(form)
.then(res => {
uni.showToast({
title: '新增成功',
icon: 'none'
})
this.formLabel = {}
done()
uni.startPullDownRefresh({})
})
.catch(() => {
unloading()
})
} else if (action == '编辑') {
update(form)
.then(res => {
uni.showToast({
title: '编辑成功',
icon: 'none'
})
this.formLabel = {}
done()
uni.startPullDownRefresh({})
})
.catch(() => {
unloading()
})
}
}
}
}
</script>
<style>
page {
background: #f6f6f6;
}
</style>

40
pages/demo/demo.config.js Normal file
View File

@ -0,0 +1,40 @@
export default [{
groupName: '基础功能',
list: [{
path: '/pages/demo/storage',
icon: 'cell',
title: 'Storage 操作',
}, {
path: '/pages/demo/vuex',
icon: 'sticky',
title: 'Vuex 操作',
}]
},
{
groupName: '网络功能',
list: [{
path: '/pages/demo/mock',
icon: 'layout',
title: 'Mock 调用',
}, {
path: '/pages/demo/request',
icon: 'tabbar',
title: 'API 调用',
}]
}, {
groupName: '业务功能',
list: [{
path: '/pages/demo/crud1',
icon: 'tag',
title: 'CRUD 操作基于Uniapp',
},{
path: '/pages/demo/crud2',
icon: 'tag',
title: 'CRUD 操作基于Uview',
},{
path: '/pages/componentsA/parse/index',
icon: 'loading',
title: '未完待续',
}]
},
]

74
pages/demo/demo.vue Normal file
View File

@ -0,0 +1,74 @@
<template>
<view class="wrap">
<page-nav :desc="desc" :title="title"></page-nav>
<view class="list-wrap">
<u-cell-group title-bg-color="rgb(243, 244, 246)" :title="getGroupTitle(item)" v-for="(item, index) in list"
:key="index">
<u-cell-item :titleStyle="{fontWeight: 500}" @click="openPage(item1.path)" :title="getFieldTitle(item1)"
v-for="(item1, index1) in item.list" :key="index1">
<image slot="icon" class="u-cell-icon" :src="getIcon(item1.icon)" mode="widthFix"></image>
</u-cell-item>
</u-cell-group>
</view>
<u-gap height="70"></u-gap>
</view>
</template>
<script>
import pageNav from '@/components/page-nav/page-nav.vue';
import list from "./demo.config.js";
export default {
components: {
pageNav
},
data() {
return {
list: list,
title: '基础功能示例合集',
desc: '众多示例覆盖开发过程的各个需求,正在不断完善中。可让您快速集成,开箱即用。'
}
},
computed: {
getIcon() {
return path => {
return 'https://cdn.uviewui.com/uview/example/' + path + '.png';
}
},
},
onShow() {
uni.setNavigationBarTitle({
title: "233"
});
},
created() {
},
methods: {
openPage(path) {
this.$u.route({
url: path
})
},
getGroupTitle(item) {
return item.groupName
},
getFieldTitle(item) {
return item.title
}
}
}
</script>
<style>
/* page {
background-color: rgb(240, 242, 244);
} */
</style>
<style lang="scss" scoped>
.u-cell-icon {
width: 36rpx;
height: 36rpx;
margin-right: 8rpx;
}
</style>

49
pages/demo/mock.vue Normal file
View File

@ -0,0 +1,49 @@
<template>
<view class="container">
<u-navbar :is-fixed="false" :border-bottom="false" :is-back="true" back-icon-name="arrow-leftward"
back-icon-color="#fff" back-icon-size="35" :background="{ background: 'linear-gradient(90deg, #69b0ff, #5f88ff)' }" title="Mock 调用"
title-color="#fff">
</u-navbar>
<view class="u-demo">
<view class="u-demo-wrap">
<view class="u-demo-title">演示效果</view>
<view class="u-demo-area u-flex u-row-center">
数据为{{data}}
</view>
</view>
<view class="u-config-wrap">
<view class="u-config-title u-border-bottom">参数操作</view>
<view class="u-config-item">
<view class="u-item-title">按钮点击</view>
<u-button @click="doRequest">获取数据</u-button>
</view>
</view>
</view>
</view>
</template>
<script>
import {
fakeBannerList
} from "@/api/mock/home.js";
export default {
data() {
return {
data: {}
};
},
methods: {
doRequest() {
let keyword = "";
fakeBannerList().then(data => {
this.data = data;
this.$u.func.showToast({
title: '数据获取成功',
})
}).catch(() => {
})
}
},
}
</script>

48
pages/demo/request.vue Normal file
View File

@ -0,0 +1,48 @@
<template>
<view class="container">
<u-navbar :is-fixed="false" :border-bottom="false" :is-back="true" back-icon-name="arrow-leftward"
back-icon-color="#fff" back-icon-size="35" :background="{ background: 'linear-gradient(90deg, #69b0ff, #5f88ff)' }" title="API 请求"
title-color="#fff">
</u-navbar>
<view class="u-demo">
<view class="u-demo-wrap">
<view class="u-demo-title">演示效果</view>
<view class="u-demo-area u-flex u-row-center">
<u-image width="300rpx" height="100rpx" :src="url"></u-image>
</view>
</view>
<view class="u-config-wßrap">
<view class="u-config-title u-border-bottom">参数操作</view>
<view class="u-config-item">
<view class="u-item-title">按钮点击</view>
<u-button @click="doRequest">刷新验证码</u-button>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
url: ''
};
},
methods: {
doRequest() {
let keyword = "";
// this.$u.api,
// captcha /api/demo.js
this.$u.api.captcha().then(data => {
this.url = data.image;
this.$u.func.showToast({
title: '数据获取成功',
})
}).catch(() => {
})
}
},
}
</script>

42
pages/demo/storage.vue Normal file
View File

@ -0,0 +1,42 @@
<template>
<view class="container">
<u-navbar :is-fixed="false" :border-bottom="false" :is-back="true" back-icon-name="arrow-leftward"
back-icon-color="#fff" back-icon-size="35" :background="{ background: 'linear-gradient(90deg, #69b0ff, #5f88ff)' }" title="Storage 操作"
title-color="#fff">
</u-navbar>
<view class="u-demo">
<view class="u-demo-wrap">
<view class="u-demo-title">演示效果</view>
<view class="u-demo-area u-flex u-row-center">
[ BladeX ] 的官网为{{domain}}
</view>
</view>
<view class="u-config-wrap">
<view class="u-config-title u-border-bottom">参数操作</view>
<view class="u-config-item">
<view class="u-item-title">按钮点击</view>
<u-button @click="modifyStorage">修改变量</u-button>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
domain: ''
}
},
onLoad() {
this.domain = uni.getStorageSync('domain');
},
methods: {
modifyStorage() {
uni.setStorageSync('domain', 'https://bladex.vip');
this.domain = uni.getStorageSync('domain');
}
},
}
</script>

41
pages/demo/vuex.vue Normal file
View File

@ -0,0 +1,41 @@
<template>
<view class="container">
<u-navbar :is-fixed="false" :border-bottom="false" :is-back="true" back-icon-name="arrow-leftward"
back-icon-color="#fff" back-icon-size="35" :background="{ background: 'linear-gradient(90deg, #69b0ff, #5f88ff)' }" title="Vuex 操作"
title-color="#fff">
</u-navbar>
<view class="u-demo">
<view class="u-demo-wrap">
<view class="u-demo-title">演示效果</view>
<view class="u-demo-area u-flex u-row-center">
<!-- vuex已做过封装可以直接使用state内的变量 -->
版本号为{{version}}
</view>
<view class="u-demo-area u-flex u-row-center">
[ BladeX ] 的作者为{{userInfo.nickName}}
</view>
</view>
<view class="u-config-wrap">
<view class="u-config-title u-border-bottom">参数操作</view>
<view class="u-config-item">
<view class="u-item-title">按钮点击</view>
<u-button @click="modifyVuex">修改变量</u-button>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
methods: {
// vuexthis.$u.vuex(key, value)
modifyVuex() {
// versionlifeData
this.$u.vuex('version', '1.0.1');
// userInfolifeData
this.$u.vuex('userInfo.nickName', '翼');
}
},
}
</script>

253
pages/home/home.vue Normal file
View File

@ -0,0 +1,253 @@
<template>
<view class="container">
<!-- 顶部标题栏 -->
<view class="header">
<text class="title">广西区直住房保障中心</text>
</view>
<!-- 预约功能区 -->
<view class="reserve-section">
<view class="reserve-card" @click="handleOnlineReserve">
<view class="icon online-icon"></view>
<text class="card-text">网上预约</text>
</view>
<view class="reserve-card" @click="handleMyReserve">
<view class="icon my-icon"></view>
<text class="card-text">我的预约</text>
</view>
</view>
<!-- 热门服务区 -->
<view class="service-section">
<text class="section-title">热门服务</text>
<view class="service-item" v-for="(item, index) in serviceList" :key="index"
@click="handleServiceClick(item)">
<view class="item-left">
<view class="dot"></view>
<text class="item-text">{{ item.name }}</text>
</view>
<view class="arrow"></view>
</view>
</view>
</view>
</template>
<script>
import {
deptList,
itemList
} from '@/api/reserve.js'
import {
dictionary
} from '@/api/system/dict.js'
export default {
data() {
return {
//
serviceList: [],
statusList: []
}
},
onLoad() {
this.getItemList()
this.getDictionary()
},
methods: {
getDictionary() {
dictionary({
code: 'booking_status'
}).then(res => {
if (res.code === 200) {
this.statusList = res.data
uni.setStorageSync('statusList', this.statusList);
}
})
},
//
handleOnlineReserve() {
uni.navigateTo({
url: '/pages/reserve/index'
})
},
//
handleMyReserve() {
uni.navigateTo({
url: '/pages/myReserve/index'
})
},
getItemList() {
let params = {
size: 5,
current: 1,
status: 1,
}
itemList(params).then(res => {
if (res.code === 200) {
this.serviceList = res.data.records
}
})
},
//
handleServiceClick(item) {
const data = JSON.stringify(item)
uni.navigateTo({
url: `/pages/reserve/notice?data=${encodeURIComponent(data)}`
})
// uni.navigateTo({
// url: `/pages/reserve/index?id=${item.id}`
// })
},
// //
// getDeptList() {
// let params = {
// size: 10,
// current: 1
// }
// deptList(params).then(res => {
// if (res.code === 200) {
// this.serviceList = res.data.records.filter(item => item.parentId != 0)
// }
// })
// }
}
}
</script>
<style scoped lang="scss">
* {
box-sizing: border-box;
}
/* 全局容器 */
.container {
min-height: 100vh;
background-color: #f5f5f5;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
/* 顶部标题栏 */
.header {
width: 100%;
height: 200rpx;
background: linear-gradient(to bottom, #e8f3ff 0%, #f5f5f5 100%);
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('/static/images/newImg/home_bg.png');
background-size: cover;
background-repeat: no-repeat;
z-index: 0;
}
.title {
font-size: 40rpx;
font-weight: 700;
color: rgba(49, 134, 255, 1);
z-index: 1;
}
/* 预约功能区 */
.reserve-section {
display: flex;
padding: 0 30rpx 30rpx;
gap: 20rpx;
}
.reserve-card {
flex: 1;
background-color: #ffffff;
border-radius: 32rpx;
padding: 40rpx 20rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
}
.icon {
width: 80rpx;
height: 80rpx;
margin-bottom: 20rpx;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.online-icon {
background-image: url('@/static/images/newImg/online_icon.png');
}
.my-icon {
background-image: url('@/static/images/newImg/my_icon.png');
}
.card-text {
font-size: 32rpx;
color: #333333;
font-weight: 500;
}
/* 热门服务区 */
.service-section {
padding: 0 30rpx;
}
.section-title {
display: block;
font-size: 34rpx;
font-weight: 600;
color: #333333;
margin-bottom: 20rpx;
}
.service-item {
display: flex;
align-items: center;
justify-content: space-between;
background-color: #ffffff;
border-radius: 24rpx;
padding: 30rpx;
margin-bottom: 16rpx;
}
.item-left {
display: flex;
align-items: center;
}
.dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: linear-gradient(to bottom, rgba(49, 134, 255, 1), rgba(21, 167, 255, 1));
margin-right: 16rpx;
}
.item-text {
font-size: 34rpx;
color: #333333;
font-weight: 500;
}
.arrow {
width: 16rpx;
height: 16rpx;
border-right: 4rpx solid #165dff;
border-top: 4rpx solid #165dff;
transform: rotate(45deg);
}
</style>

164
pages/loading/index.vue Normal file
View File

@ -0,0 +1,164 @@
<template>
<!-- 空白loading页 -->
<view class="loading-container">
<view class="loading-text">正在登录中请稍候...</view>
</view>
</template>
<script>
import {
login
} from '@/api/login.js'
export default {
onLoad(options) {
//
this.getToken()
this.autoLogin(options)
},
methods: {
getToken() {
return new Promise((resolve, reject) => {
uni.request({
url: '/epoint-web-cs/rest/oauth2/token',
method: 'POST',
header: {
'Content-Type': 'application/x-www-form-urlencoded',
},
data: {
client_id: '316f983b-2b93-4c57-b912-c00e05aceed2', //
client_secret: 'cd2cf239-1506-47bb-9b63-507b6b759a06',
grant_type: 'password',
platform: 'mobile',
username: '马贤宝',
password: 'Epoint@123',
//
},
success: (res) => {
const data = res.data
console.log(data, 'data')
//
if (data.status.code === 1) {
resolve(data.custom) // name, phone, idCard
} else {
reject(new Error(data.status.text || '获取用户信息失败'))
}
}
})
})
},
/**
* 自动登录流程
*/
async autoLogin(options) {
try {
// 1. URLtoken
const token = options.token
if (!token) {
uni.showToast({
title: '未获取到登录凭证',
icon: 'none'
})
return
}
// 2. 使token
const userInfo = await this.getUserInfo(token)
// 4.
this.doLocalLogin(userInfo)
// 5.
// uni.switchTab({
// url: '/pages/home/home' //
// })
} catch (err) {
console.error('登录失败:', err)
uni.showToast({
title: err.message || '登录失败,请重试',
icon: 'none',
duration: 3000
})
}
},
/**
* 请求接口获取用户信息你给的正式接口
* @param {string} token
*/
getUserInfo(token) {
return new Promise((resolve, reject) => {
let url = '/epoint-web-cs/rest/wechatloginrest/getuserinfo'
// if (process.env.NODE_ENV === 'development') {
// //
// url = 'epoint-web-cs/rest/wechatloginrest/getuserinfo'
// } else if (process.env.NODE_ENV === 'production') {
// //
// url = 'epoint-web/rest/wechatloginrest/getuserinfo'
// }
uni.request({
url: url,
method: 'POST',
header: {
'Content-Type': 'application/x-www-form-urlencoded',
'authorization': 'Bearer ' + token // token
},
success: (res) => {
const data = res.data
//
if (data.status.code === 1) {
resolve(data.custom) // name, phone, idCard
} else {
reject(new Error(data.status.text || '获取用户信息失败'))
}
}
})
})
},
/**
* 你系统本地登录缓存token + 用户信息
*/
doLocalLogin(userInfo) {
login({
// username: userInfo.idnumber,
username: userInfo.mobile,
grant_type: "third"
})
.then(data => {
this.$u.func.login(data, '/pages/home/home')
})
.catch(err => {
this.$u.func.showToast({
title: '用户名或密码错误'
})
})
}
}
}
</script>
<style scoped>
.loading-container {
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
}
.loading-text {
font-size: 28rpx;
color: #666;
}
</style>

View File

@ -0,0 +1,261 @@
<template>
<view class="container">
<view class="content">
<view class="top">
<view class="cell">
<view class="name">账号</view>
<view class="input-box">
<input type="text" v-model="username" placeholder="请输入账号" class="ipt" placeholder-class="hold"
@blur="handleInputCheck" />
</view>
</view>
<view class="cell">
<view class="name">密码</view>
<view class="input-box">
<input type="password" v-model="password" placeholder="请输入密码" class="ipt"
placeholder-class="hold" @blur="handleInputCheck" />
</view>
</view>
<button class="submit" @click="submit" :disabled="disabled">登录</button>
</view>
</view>
<!-- -->
</view>
</template>
<script>
import sm2 from '@/utils/sm2.js'
import {
publicKey
} from '@/common/setting'
export default {
onLoad(option) {
const {
redirect
} = option
if (redirect) this.redirect = redirect
},
data() {
return {
tenantId: '000000',
username: '',
password: '',
disabled: false,
redirect: '/pages/home/home'
}
},
methods: {
submit() {
this.$u.api
.token(this.tenantId, this.username, sm2.doEncrypt(this.password, publicKey, 0))
.then(data => {
this.$u.func.login(data, this.redirect)
})
.catch(err => {
console.log(err)
this.$u.func.showToast({
title: '用户名或密码错误'
})
})
},
handleInputCheck() {
this.disabled = false
}
}
}
</script>
<style lang="scss">
.container {
min-height: 100vh;
overflow: hidden;
.set-icon {
vertical-align: middle;
width: 41rpx;
height: auto;
margin-right: 35rpx;
}
}
.content {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
height: 90vh;
width: 100%;
.top {
width: 100%;
}
.logo {
display: block;
width: 281rpx;
height: auto;
margin: 0 auto 120rpx;
}
.cell {
width: 100%;
padding: 0 85rpx;
box-sizing: border-box;
margin-top: 36rpx;
.name {
font-size: 22rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #3e4a59;
line-height: 30rpx;
opacity: 0.72;
}
.input-box {
padding: 30rpx 0;
border-bottom: 2rpx solid #f6f6f6;
display: flex;
align-items: center;
.code {
font-size: 22rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #0d0d0d;
line-height: 30rpx;
text {
// color: #14b9c8;
color: #5f88ff;
}
}
.ipt {
flex: 1;
// height: 24rpx;
font-size: 24rpx;
}
.hold {
font-size: 26rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #3e4a59;
line-height: 30px;
opacity: 0.45;
}
}
}
.agree {
margin: 27rpx 95rpx 0;
font-size: 22rpx;
font-family: Adobe Heiti Std;
font-weight: normal;
color: #cacaca;
line-height: 34rpx;
.a {
color: #000000;
}
}
.submit {
margin: 60rpx 90rpx 0;
border: none;
width: 572rpx;
height: 86rpx;
line-height: 86rpx;
box-sizing: border-box;
border-radius: 15rpx;
background-color: #5f88ff;
color: #ffffff;
&::after {
content: none;
}
&::before {
content: none;
}
&[disabled='true'] {
background: #e4e4e4;
font-size: 36rpx;
font-family: Source Han Sans CN;
font-weight: 500;
color: #ffffff;
}
}
.tip {
margin-top: 30rpx;
text-align: center;
font-size: 22rpx;
font-family: Adobe Heiti Std;
font-weight: normal;
color: #cacaca;
line-height: 34rpx;
}
.change {
margin-top: 20rpx;
text-align: center;
font-size: 22rpx;
font-family: Adobe Heiti Std;
font-weight: normal;
// color: #14b9c8;
color: #5f88ff;
line-height: 34rpx;
}
.tag {
display: flex;
justify-content: center;
align-items: center;
font-size: 22rpx;
font-family: Adobe Heiti Std;
font-weight: normal;
color: #9f9f9f;
line-height: 34rpx;
&::before {
content: '';
display: block;
width: 160rpx;
height: 1px;
background: #d8d8d8;
opacity: 0.86;
}
&::after {
content: '';
display: block;
width: 160rpx;
height: 1px;
background: #d8d8d8;
opacity: 0.86;
}
}
.chat-arr {
margin-top: 50rpx;
display: flex;
align-items: center;
justify-content: space-between;
.icon {
width: 73rpx;
height: 73rpx;
}
}
}
</style>

291
pages/login/login-phone.vue Normal file
View File

@ -0,0 +1,291 @@
<template>
<view class="container">
<u-navbar :is-fixed="false" :border-bottom="false" :is-back="false" back-icon-name="arrow-leftward" title="登录"
:background="{ background: '#fff' }" title-color="#000000">
<view class="" slot="right">
<image src="/static/images/user/c8.png" class="set-icon" mode="widthFix"></image>
</view>
</u-navbar>
<view class="content">
<view class="top">
<image src="/static/images/logo.png" class="logo" mode="widthFix"></image>
<view class="cell">
<view class="name">手机号</view>
<view class="input-box">
<input type="number" v-model="phone" placeholder="请输入手机号码" class="ipt" placeholder-class="hold"
@blur="handleInputCheck" />
</view>
</view>
<view class="cell">
<view class="name">短信验证码</view>
<view class="input-box">
<input type="number" v-model="code" placeholder="请输入验证码" class="ipt" placeholder-class="hold"
@blur="handleInputCheck" />
<view class="code" @click="sendCode" v-if="count === 60">获取验证码</view>
<view class="code" v-else><text>{{ count }}</text>秒重新获取</view>
</view>
</view>
<!-- <view class="agree">
登录即代表同意
<text class="a">用户协议</text>
<text class="a">隐私政策</text>
</view> -->
<button class="submit" @click="submit" :disabled="disabled">登录</button>
<view class="tip">未注册用户验证后将自动注册并登录</view>
<navigator url="/pages/login/login-account" hover-class="none" class="change">密码登录 ></navigator>
</view>
<!-- 社交账号登录 -->
<view class="bottom">
<view class="tag">社交账号登录</view>
<view class="chat-arr">
<image src="/static/images/wx.png" class="icon" mode=""></image>
<image src="/static/images/qq.png" class="icon" mode=""></image>
<image src="/static/images/wb.png" class="icon" mode=""></image>
</view>
</view>
</view>
<!-- -->
</view>
</template>
<script>
export default {
data() {
return {
phone: '',
code: '',
disabled: true,
count: 60,
timer: ''
};
},
methods: {
submit() {
this.$u.func.showToast({
title: '新版本即将到来',
})
},
handleInputCheck() {
if (!this.phone && !/^1\d{10}$/.test(this.phone)) {
this.disabled = true
return uni.showToast({
title: '请输入正确的手机号',
duration: 2000,
icon: 'none'
});
}
if (!this.code) {
this.disabled = true
return uni.showToast({
title: '请输入验证码',
duration: 2000,
icon: 'none'
});
}
this.disabled = false
},
async sendCode() {
this.count = this.count - 1;
this.timer = setInterval(() => {
if (this.count == 0) {
clearInterval(this.timer);
this.count = 60;
return;
}
this.count = this.count - 1;
}, 1000);
}
}
};
</script>
<style lang="scss">
.container {
min-height: 100vh;
overflow: hidden;
.set-icon {
vertical-align: middle;
width: 41rpx;
height: auto;
margin-right: 35rpx;
}
}
.content {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
height: 90vh;
width: 100%;
.top {
width: 100%;
}
.logo {
display: block;
width: 281rpx;
height: auto;
margin: 0 auto 120rpx;
}
.cell {
width: 100%;
padding: 0 85rpx;
box-sizing: border-box;
margin-top: 36rpx;
.name {
font-size: 22rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #3e4a59;
line-height: 30rpx;
opacity: 0.72;
}
.input-box {
padding: 30rpx 0;
border-bottom: 2rpx solid #f0f2f5;
display: flex;
align-items: center;
.code {
font-size: 22rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #0d0d0d;
line-height: 30rpx;
text {
// color: #14b9c8;
color: #5f88ff;
}
}
.ipt {
flex: 1;
// height: 24rpx;
font-size: 24rpx;
}
.hold {
font-size: 26rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #3e4a59;
line-height: 30px;
opacity: 0.45;
}
}
}
.agree {
margin: 27rpx 95rpx 0;
font-size: 22rpx;
font-family: Adobe Heiti Std;
font-weight: normal;
color: #cacaca;
line-height: 34rpx;
.a {
color: #000000;
}
}
.submit {
margin: 60rpx 90rpx 0;
border: none;
width: 572rpx;
height: 86rpx;
line-height: 86rpx;
box-sizing: border-box;
border-radius: 15rpx;
background-color: #5f88ff;
color: #ffffff;
&::after {
content: none;
}
&::before {
content: none;
}
&[disabled='true'] {
background: #e4e4e4;
font-size: 36rpx;
font-family: Source Han Sans CN;
font-weight: 500;
color: #ffffff;
}
}
.tip {
margin-top: 30rpx;
text-align: center;
font-size: 22rpx;
font-family: Adobe Heiti Std;
font-weight: normal;
color: #cacaca;
line-height: 34rpx;
}
.change {
margin-top: 20rpx;
text-align: center;
font-size: 22rpx;
font-family: Adobe Heiti Std;
font-weight: normal;
color: #69b0ff;
line-height: 34rpx;
}
.tag {
display: flex;
justify-content: center;
align-items: center;
font-size: 22rpx;
font-family: Adobe Heiti Std;
font-weight: normal;
color: #9f9f9f;
line-height: 34rpx;
&::before {
content: '';
display: block;
width: 160rpx;
height: 1px;
background: #d8d8d8;
opacity: 0.86;
}
&::after {
content: '';
display: block;
width: 160rpx;
height: 1px;
background: #d8d8d8;
opacity: 0.86;
}
}
.chat-arr {
margin-top: 50rpx;
display: flex;
align-items: center;
justify-content: space-between;
.icon {
width: 73rpx;
height: 73rpx;
}
}
}
</style>

377
pages/manage/dept.vue Normal file
View File

@ -0,0 +1,377 @@
<template>
<view class="container">
<view class="safe-area-inset-bottom">
<rider-curd
v-model="form"
:option="option"
:data="list"
:rules="rules"
:readonly.sync="readonly"
@search="onSearch"
@child="onChildren"
@action-click="onActionClick"
@submit="onSubmit"
@del="onRemove"
ref="crud"
>
<template #form>
<u-form-item label="机构名称" prop="deptName" :label-width="option.labelWidth || 160" required>
<u-input v-model="form.deptName" :disabled="readonly" placeholder="机构名称" />
</u-form-item>
<u-form-item label="机构全称" prop="fullName" :label-width="option.labelWidth || 160" required>
<u-input v-model="form.fullName" :disabled="readonly" placeholder="机构全称" />
</u-form-item>
<u-form-item label="上级机构" prop="parentId" :label-width="option.labelWidth || 160" required>
<u-input
v-if="!isAddChild"
v-model="formLabel.parentName"
placeholder="机构全称"
type="select"
@click="
() => {
if (!readonly) $refs.treePicker._show()
}
"
/>
<u-input v-else v-model="formLabel.parentName" placeholder="机构全称" disabled />
</u-form-item>
<u-form-item label="机构类型" prop="deptCategory" :label-width="option.labelWidth || 160" required>
<u-input
v-model="formLabel.category"
placeholder="机构类型"
type="select"
@click="
() => {
if (!readonly) categoryShow = true
}
"
/>
</u-form-item>
<u-form-item label="排序" prop="sort" :label-width="option.labelWidth || 160" required>
<u-input v-model="form.sort" :disabled="readonly" type="number" placeholder="排序" />
</u-form-item>
<u-form-item label="描述" :label-width="option.labelWidth || 160" prop="remark">
<u-input v-model="form.remark" :disabled="readonly" type="textarea" :maxlength="-1" :height="35" placeholder="描述" />
</u-form-item>
</template>
<template #loadmore>
<u-loadmore :status="loadStatus" @loadmore="getList()" />
</template>
</rider-curd>
</view>
<u-select
v-model="categoryShow"
:list="categoryList"
value-name="dictKey"
label-name="dictValue"
@confirm="onConfirm"
safe-area-inset-bottom
></u-select>
<!-- 上级机构tree -->
<rider-tree
ref="treePicker"
:range="deptTreeList"
idKey="id"
nameKey="title"
:multiple="false"
:cascade="false"
:foldAll="false"
confirmColor="#4E7FF9"
cancelColor="#757575"
title="上级机构"
titleColor="#757575"
@cancel="$refs.treePicker._hide()"
@confirm="onTreeConfirm"
></rider-tree>
</view>
</template>
<script>
import riderCurd from '@/components/rider-crud/index'
import riderTree from '@/components/rider-crud/components/tree-picker'
import { getList, add, update, remove, deptTree } from '@/api/system/dept.js'
import { tenantSelect } from '@/api/system/tenant.js'
import { dictionary } from '@/api/system/dict.js'
import { getDicLabel } from '@/components/rider-crud/util/tool.js'
export default {
components: { riderCurd, riderTree },
data() {
return {
checkedSwitch: false,
form: {},
formLabel: {},
list: [],
params: {
current: 1,
size: 10,
parentId: 0
},
loadStatus: 'loadmore', //
readonly: false, //
option: {
navTitle: '部门管理', //
manageBtn: true, //
searchShow: true, //
labelWidth: 160, //formlable 160rpx
column: [
{
label: '机构名称',
prop: 'deptName'
},
{
label: '所属租户',
prop: 'tenantName'
},
{
label: '机构全称',
prop: 'fullName'
},
{
label: '机构类型',
prop: 'deptCategoryName'
},
{
label: '排序',
prop: 'sort'
},
{
label: '描述',
prop: 'remark'
}
],
actions: [
{
name: '编辑', //
icon: 'edit-pen', //
color: '#333', //
fontsize: 30, //rpx
width: 40, // px
background: '#fff' //
},
{
name: '删除',
icon: 'trash',
color: '#333',
fontsize: 30,
width: 40,
background: '#fff'
},
{
name: '新增子项',
icon: 'plus',
color: '#333',
fontsize: 30,
width: 40,
background: '#fff'
}
]
},
rules: {
deptCategory: [{ required: true, message: '请选择机构类型', trigger: 'change', type: 'number' }],
deptName: [{ required: true, message: '请输入机构名称', trigger: 'blur' }],
fullName: [{ required: true, message: '请输入机构全称', trigger: 'blur' }],
sort: [{ required: true, message: '请输入排序', trigger: 'blur', type: 'number' }]
},
categoryShow: false,
categoryList: [],
tenantList: [],
deptTreeList: [],
isAddChild: false
}
},
onReady() {
this.getList(true)
this.getTenant().then(() => {
this.getList(true)
})
this.getCategoryList()
},
onPullDownRefresh() {
this.getList(true)
},
onReachBottom() {
if (this.loadStatus == 'nomore') return
this.getList()
},
methods: {
//
getList(override = false) {
if (override) {
this.params.current = 1
}
const { size } = this.params
getList(this.params)
.then(res => {
const records = res.data
if (records.length < size) this.loadStatus = 'nomore'
else this.loadStatus = 'loadmore'
if (override) this.list = records
else this.list = this.list.concat(records)
if (this.list && this.list.length > 0) {
this.list.map(e => {
e.tenantName = getDicLabel(e.tenantId, this.tenantList, { label: 'tenantName', value: 'tenantId' })
e.children = []
})
this.$refs.crud.setLazyLoad((item, index, cb) => {
getList({ parentId: item.id }).then(res => {
res.data.map(e => {
e.tenantName = getDicLabel(e.tenantId, this.tenantList, { label: 'tenantName', value: 'tenantId' })
})
cb(res.data)
})
})
}
this.params.current++
uni.stopPullDownRefresh()
})
.catch(err => {
uni.stopPullDownRefresh()
})
},
//
getCategoryList() {
dictionary({ code: 'org_category' }).then(res => {
this.categoryList = res.data
})
},
//
getTenant() {
return new Promise(resolve => {
deptTree().then(res => {
this.deptTreeList = res.data
})
tenantSelect().then(res => {
this.tenantList = res.data
resolve()
})
})
},
//
onConfirm(e) {
this.$set(this.formLabel, 'category', e[0].label)
this.$set(this.form, 'deptCategory', Number(e[0].value))
},
//
onTreeConfirm(e) {
this.$set(this.form, 'parentId', e[0].id)
this.$set(this.formLabel, 'parentName', e[0].name)
},
// ()
onSearch(val) {
this.params = {
current: 1,
size: 10,
deptName: val
}
this.getList(true)
},
//
onActionClick({ index, data, action, done }) {
const { name } = action
switch (name) {
case '新增':
this.form = {}
this.formLabel = {}
this.isAddChild = false
done()
break
case '删除':
this.onRemove(data.id)
break
case '查看':
this.readonly = true
case '编辑':
this.isAddChild = false
this.formLabel = {
category: getDicLabel(data.deptCategory, this.categoryList),
parentName: getDicLabel(data.parentId, this.deptTreeList, { label: 'title', value: 'id' })
}
done()
break
case '新增子项':
this.isAddChild = true
this.form = {}
this.formLabel = {
parentName: getDicLabel(data.id, this.deptTreeList, { label: 'title', value: 'id' })
}
this.$set(this.form, 'parentId', data.id)
done()
break
}
if (['删除', '编辑', '新增子项'].includes(name)) this.$refs['crud'].hideChildren()
},
//
onRemove(id) {
uni.showModal({
title: '确定要删除吗?',
success: action => {
if (action.confirm) {
remove(id).then(() => {
uni.showToast({
title: '删除成功',
icon: 'none'
})
setTimeout(() => {
this.$refs['crud'].allCheckHide(false)
}, 100)
// #ifdef APP
this.getList(true)
// #endif
// #ifndef APP
uni.startPullDownRefresh({})
// #endif
})
}
}
})
},
//
onSubmit(form, action, unloading, done) {
if (action == '新增' || action == '新增子项') {
add(form)
.then(res => {
uni.showToast({
title: '新增成功',
icon: 'none'
})
done()
uni.startPullDownRefresh({})
})
.catch(() => {
unloading()
})
} else if (action == '编辑') {
update(form)
.then(res => {
uni.showToast({
title: '编辑成功',
icon: 'none'
})
done()
uni.startPullDownRefresh({})
})
.catch(() => {
unloading()
})
}
}
}
}
</script>
<style>
page {
background: #f6f6f6;
}
</style>
<style lang="scss" scoped>
.nav-right {
color: #fff;
font-size: 28rpx;
font-weight: normal;
padding-right: 24rpx;
}
</style>

489
pages/manage/dict.vue Normal file
View File

@ -0,0 +1,489 @@
<template>
<view class="container">
<view class="safe-area-inset-bottom">
<rider-curd
v-model="form"
:option="option"
:data="list"
:rules="rules"
:readonly.sync="readonly"
@search="onSearch"
@action-click="onActionClick"
@submit="onSubmit"
@del="onRemove"
ref="crud"
>
<template #form>
<u-form-item label="字典编号" prop="code" :label-width="option.labelWidth || 160" required>
<u-input v-model="form.code" :disabled="readonly" placeholder="字典编号" />
</u-form-item>
<u-form-item label="字典名称" prop="dictValue" :label-width="option.labelWidth || 160" required>
<u-input v-model="form.dictValue" :disabled="readonly" placeholder="字典名称" />
</u-form-item>
<u-form-item label="字典排序" prop="sort" :label-width="option.labelWidth || 160" required>
<u-input v-model="form.sort" :disabled="readonly" type="number" placeholder="字典排序" />
</u-form-item>
<u-form-item label="封存" prop="isSealed" :label-width="option.labelWidth || 160" required>
<u-radio-group v-model="form.isSealed" :disabled="readonly">
<u-radio shape="circle" :name="1" active-color="#4E7FF9"></u-radio>
<u-radio shape="circle" :name="0" active-color="#4E7FF9"></u-radio>
</u-radio-group>
</u-form-item>
<u-form-item label="字典备注" :label-width="option.labelWidth || 160" prop="remark">
<u-input
v-model="form.remark"
:disabled="readonly"
type="textarea"
:maxlength="-1"
:height="35"
placeholder="字典备注"
/>
</u-form-item>
</template>
<template #loadmore>
<u-loadmore :status="loadStatus" @loadmore="getList()" />
</template>
</rider-curd>
</view>
<!-- 字典配置 -->
<u-popup v-model="configShow" mode="bottom" height="100vh" @close="onConfigClose" :mask-close-able="false">
<scroll-view scroll-y="true" style="height: 100vh; background: #f5f5f5">
<rider-curd
ref="childCrud"
v-model="childForm"
:navBack="onConfigClose"
@search="onChildSearch"
:option="{ ...configOption, navTitle: `${configName} - 字典配置` }"
:data="childList"
:rules="rules"
:readonly.sync="readonly"
@action-click="onActionClick"
@submit="onSubmit"
@del="onRemove"
>
<template #form>
<u-form-item label="字典编号" prop="code" :label-width="configOption.labelWidth || 160" required>
<u-input v-model="childForm.code" disabled placeholder="字典编号" />
</u-form-item>
<u-form-item label="字典名称" prop="dictValue" :label-width="configOption.labelWidth || 160" required>
<u-input v-model="childForm.dictValue" :disabled="readonly" placeholder="字典名称" />
</u-form-item>
<u-form-item label="上级字典" :label-width="configOption.labelWidth || 160">
<u-input v-model="parentData.dictValue" disabled placeholder="上级字典" />
</u-form-item>
<u-form-item label="字典键值" prop="dictKey" :label-width="configOption.labelWidth || 160" required>
<u-input v-model="childForm.dictKey" :disabled="readonly" placeholder="字典键值" />
</u-form-item>
<u-form-item label="字典排序" prop="sort" :label-width="configOption.labelWidth || 160" required>
<u-input v-model="childForm.sort" :disabled="readonly" type="number" placeholder="字典排序" />
</u-form-item>
<u-form-item label="封存" prop="isSealed" :label-width="configOption.labelWidth || 160" required>
<u-radio-group v-model="childForm.isSealed" :disabled="readonly">
<u-radio shape="circle" :name="1" active-color="#4E7FF9"></u-radio>
<u-radio shape="circle" :name="0" active-color="#4E7FF9"></u-radio>
</u-radio-group>
</u-form-item>
<u-form-item label="字典备注" prop="remark" :label-width="configOption.labelWidth || 160">
<u-input
v-model="childForm.remark"
:disabled="readonly"
type="textarea"
:maxlength="-1"
:height="35"
placeholder="字典备注"
/>
</u-form-item>
</template>
<template #loadmore>
<u-loadmore :status="childLoadStatus" @loadmore="getChild()" />
</template>
</rider-curd>
</scroll-view>
</u-popup>
</view>
</template>
<script>
import riderCurd from '@/components/rider-crud/index'
import { parentList, add, update, remove, dictionary, child, dictTree } from '@/api/system/dict.js'
import { getDicLabel } from '@/components/rider-crud/util/tool.js'
export default {
components: { riderCurd },
data() {
return {
configShow: false, //
form: {
isSealed: 0
},
list: [],
params: {
current: 1,
size: 10
},
readonly: false, //
parentData: {}, //
childList: [],
childForm: {
isSealed: 0
},
childParams: {
current: 1,
size: 10,
parentId: null
},
loadStatus: 'loadmore', //
childLoadStatus: 'loadmore', //
option: {
navTitle: '字典管理', //
manageBtn: true, //
searchShow: true, //
labelWidth: 160, //formlable 160rpx
column: [
{
label: '字典编号',
prop: 'code'
},
{
label: '字典名称',
prop: 'dictValue'
},
{
label: '字典排序',
prop: 'sort'
},
{
label: '封存',
prop: 'sealedName'
},
{
label: '字典备注',
prop: 'remark'
}
],
actions: [
{
name: '编辑', //
icon: 'edit-pen', //
color: '#333', //
fontsize: 30, //rpx
width: 40, // px
background: '#fff' //
},
{
name: '删除',
icon: 'trash',
color: '#333',
fontsize: 30,
width: 40,
background: '#fff'
},
{
name: '设置',
icon: 'setting',
color: '#333',
fontsize: 30,
width: 40,
background: '#fff'
}
]
},
configName: '',
configOption: {
navBackIcon: 'close', //icon
manageBtn: true, //
searchShow: true, //
labelWidth: 160, //formlable 160rpx
column: [
{
label: '字典编号',
prop: 'code'
},
{
label: '字典名称',
prop: 'dictValue'
},
{
label: '字典键值',
prop: 'dictKey'
},
{
label: '字典排序',
prop: 'sort'
},
{
label: '封存',
prop: 'sealedName'
},
{
label: '字典备注',
prop: 'remark'
}
],
actions: [
{
name: '编辑', //
icon: 'edit-pen', //
color: '#333', //
fontsize: 30, //rpx
width: 40, // px
background: '#fff' //
},
{
name: '删除',
icon: 'trash',
color: '#333',
fontsize: 30,
width: 40,
background: '#fff'
},
{
name: '新增子项',
icon: 'plus',
color: '#333',
fontsize: 30,
width: 40,
background: '#fff'
}
]
},
rules: {
code: [{ required: true, message: '请输入字典编号', trigger: 'blur' }],
dictValue: [{ required: true, message: '请输入字典名称', trigger: 'blur' }],
sort: [{ required: true, message: '请输入字典排序', trigger: 'blur', type: 'number' }],
isSealed: [{ required: true, message: '请选择是否封存', trigger: 'blur', type: 'number' }],
dictKey: [{ required: true, message: '请输入字典键值', trigger: 'blur' }]
}
}
},
onReady() {
this.getList(true)
},
onPullDownRefresh() {
this.getList(true)
this.getChild(true)
},
onReachBottom() {
if (this.loadStatus == 'nomore') return
this.getList()
if (this.childLoadStatus == 'nomore') return
this.getChild()
},
methods: {
//
getList(override = false) {
if (override) {
this.params.current = 1
}
const { size } = this.params
parentList(this.params)
.then(res => {
const { records } = res.data
if (records.length < size) this.loadStatus = 'nomore'
else this.loadStatus = 'loadmore'
if (override) {
this.list = records
} else {
this.list = this.list.concat(records)
}
if (this.list && this.list.length > 0) {
this.list.map(e => {
e.sealedName = e.isSealed ? '是' : '否'
})
}
this.params.current++
uni.stopPullDownRefresh()
})
.catch(err => {
uni.stopPullDownRefresh()
})
},
//
getChild(override = false) {
if (override) {
this.childParams.current = 1
}
const { size } = this.childParams
child(this.childParams)
.then(res => {
const records = res.data
if (records.length < size) this.childLoadStatus = 'nomore'
else this.childLoadStatus = 'loadmore'
if (override) {
this.childList = records
} else {
this.childList = this.childList.concat(records)
}
if (this.childList && this.childList.length > 0) {
this.childList.map(e => {
e.sealedName = e.isSealed ? '是' : '否'
})
}
this.childParams.current++
uni.stopPullDownRefresh()
})
.catch(err => {
uni.stopPullDownRefresh()
})
},
//
onActionClick({ index, data, action, done }) {
const { name } = action
switch (name) {
case '新增':
if (this.parentData.code) {
this.$set(this.childForm, 'code', this.parentData.code)
this.$set(this.childForm, 'parentId', this.parentData.id)
this.$set(this.childParams, 'parentId', this.parentData.id)
}
done()
break
case '删除':
this.onRemove(data.id)
break
case '查看':
this.readonly = true
case '编辑':
if (data.id) done()
break
case '设置':
this.childForm = {}
this.parentData = data
this.configName = data.dictValue
this.$set(this.childParams, 'parentId', data.id)
this.getChild(true)
setTimeout(() => {
this.configShow = true
}, 100)
break
case '新增子项':
this.childForm = {}
this.parentData = data
this.$set(this.childForm, 'code', data.code)
this.$set(this.childForm, 'parentId', data.id)
this.$set(this.childParams, 'parentId', data.id)
done()
break
}
if (['删除', '编辑', '新增子项'].includes(name)) {
this.$refs['crud'].hideChildren()
this.$refs['childCrud'].hideChildren()
}
},
//
onRemove(id) {
uni.showModal({
title: '确定要删除吗?',
success: action => {
if (action.confirm) {
remove(id).then(() => {
uni.showToast({
title: '删除成功',
icon: 'none'
})
setTimeout(() => {
this.$refs['crud'].allCheckHide(false)
}, 100)
// #ifdef APP
this.getList(true)
// #endif
// #ifndef APP
uni.startPullDownRefresh({})
// #endif
})
}
}
})
},
//
onSubmit(form, action, unloading, done) {
if (action == '新增' || action == '新增子项') {
add(form)
.then(res => {
uni.showToast({
title: '新增成功',
icon: 'none'
})
done()
uni.startPullDownRefresh({})
})
.catch(() => {
unloading()
})
} else if (action == '编辑') {
update(form)
.then(res => {
uni.showToast({
title: '编辑成功',
icon: 'none'
})
done()
uni.startPullDownRefresh({})
})
.catch(() => {
unloading()
})
}
},
//
onConfigClose() {
this.configShow = false
this.childParams = {
current: 1,
size: 10,
parentId: null
}
this.parentData = {}
this.form = { isSealed: 0 }
this.childForm = { isSealed: 0 }
setTimeout(() => {
this.getChild(true)
})
},
// ()
onSearch(val) {
this.params = {
current: 1,
size: 10,
dictValue: val
}
this.getList(true)
},
//
onChildSearch(val) {
this.childForm = {
current: 1,
size: 10,
dictValue: val
}
this.getChild(true)
}
}
}
</script>
<style>
page {
background: #f6f6f6;
}
</style>
<style lang="scss" scoped>
.nav-right {
color: #fff;
font-size: 28rpx;
font-weight: normal;
padding-right: 24rpx;
}
.config-title {
padding: 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 20rpx solid #f5f5f5;
color: #fff;
background: linear-gradient(45deg, #4e7ff9 0%, #59a5fb 100%);
}
</style>

166
pages/manage/manage.vue Normal file
View File

@ -0,0 +1,166 @@
<template>
<view class="container">
<u-navbar
:is-fixed="false"
:border-bottom="false"
:is-back="false"
title="管理"
:background="{ background: 'linear-gradient(90deg, #69b0ff, #5f88ff)' }"
title-color="#fff"
>
<image slot="right" src="/static/images/home/message.png" class="message-icon" mode="widthFix"></image>
</u-navbar>
<view class="tab">
<u-tabs
:list="list"
:is-scroll="false"
:current="current"
active-color="#5f88ff"
inactive-color="#595959"
height="100"
@change="change"
></u-tabs>
</view>
<swiper id="swiperBox" :style="{ height: swiperHeight + 'px' }" :current="current" @change="tabsChange">
<swiper-item class="swiper-item" key="0">
<scroll-view scroll-y style="width: 100%;height: 100%;" @scrolltolower="onreachBottom">
<view class="content">
<u-grid :col="3">
<u-grid-item v-for="(_item, _index) in deskList" :key="_index">
<navigator :url="_item.path" hover-class="none" class="gitem">
<image :src="require(`@/static/images/manage/${_item.type}.png`)" class="img" mode="widthFix"></image>
<view class="name">{{ _item.name }}</view>
</navigator>
</u-grid-item>
</u-grid>
</view>
</scroll-view>
</swiper-item>
<swiper-item class="swiper-item" key="1">
<scroll-view scroll-y style="width: 100%;height: 100%;" @scrolltolower="onreachBottom">
<view class="content">
<u-grid :col="3">
<u-grid-item v-for="(_item, _index) in systemList" :key="_index">
<navigator :url="_item.path" hover-class="none" class="gitem">
<image :src="require(`@/static/images/manage/${_item.type}.png`)" class="img" mode="widthFix"></image>
<view class="name">{{ _item.name }}</view>
</navigator>
</u-grid-item>
</u-grid>
</view>
</scroll-view>
</swiper-item>
</swiper>
</view>
</template>
<script>
export default {
data() {
return {
current: 0,
swiperHeight: 0,
list: [
{
name: '工作台'
},
{
name: '系统'
},
],
deskList: [
{
name: '公告',
type: 'notice',
path: '/pages/manage/notice'
},
],
systemList: [
{
name: '角色',
type: 'role',
path: '/pages/manage/role'
},
{
name: '部门',
type: 'dept',
path: '/pages/manage/dept'
},
{
name: '字典',
type: 'dict',
path: '/pages/manage/dict'
},
{
name: '岗位',
type: 'post',
path: '/pages/manage/post'
}
]
}
},
onReady() {
let that = this
uni.getSystemInfo({
success(e) {
console.log(e)
let { windowWidth, windowHeight, safeArea } = e
const query = uni.createSelectorQuery().in(that)
query
.select('#swiperBox')
.boundingClientRect(data => {
that.swiperHeight = safeArea.bottom - data.top
})
.exec()
}
})
},
methods: {
tabsChange(e) {
this.current = e.detail.current
},
change(index) {
this.current = index
}
}
}
</script>
<style lang="scss">
.swiper-box {
height: 100vh;
}
.message-icon {
width: 32rpx;
height: auto;
margin-right: 27rpx;
}
.tab {
margin-bottom: 10rpx;
}
.gitem {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
.img {
width: 54rpx;
height: auto;
margin-bottom: 26rpx;
}
font-size: 28rpx;
font-family: PingFang SC;
font-weight: bold;
color: #030305;
line-height: 36rpx;
}
</style>

326
pages/manage/notice.vue Normal file
View File

@ -0,0 +1,326 @@
<template>
<view class="container">
<view class="safe-area-inset-bottom">
<rider-curd
v-model="form"
:option="option"
:data="list"
:rules="rules"
:readonly.sync="readonly"
@search="onSearch"
@action-click="onActionClick"
@submit="onSubmit"
@del="onRemove"
ref="crud"
>
<view slot="form">
<u-form-item label="通知标题" prop="title" :label-width="option.labelWidth || 160" required>
<u-input v-model="form.title" :disabled="readonly" placeholder="通知标题" />
</u-form-item>
<u-form-item label="通知类型" prop="category" :label-width="option.labelWidth || 160" required>
<u-input
v-model="formLabel.category"
:disabled="readonly"
placeholder="通知类型"
type="select"
@click="
() => {
if (!readonly) categoryShow = true
}
"
/>
</u-form-item>
<u-form-item label="通知日期" prop="releaseTime" :label-width="option.labelWidth || 160" required>
<u-input
v-model="form.releaseTime"
:disabled="readonly"
placeholder="通知日期"
type="select"
@click="
() => {
if (!readonly) timeShow = true
}
"
/>
</u-form-item>
<u-form-item label="通知内容" :label-width="option.labelWidth || 160" :border-bottom="false">
<template #right v-if="!readonly">
<u-button
@click="onRichEdit(form.content)"
ripple
:custom-style="{ background: '#FFF6E9', color: '#FAA646' }"
size="mini"
>
去编辑
</u-button>
</template>
</u-form-item>
<mp-html :content="form.content"></mp-html>
</view>
<template #loadmore>
<u-loadmore :status="loadStatus" @loadmore="getList()" />
</template>
</rider-curd>
</view>
<!-- 通知类型 -->
<u-select
v-model="categoryShow"
:list="categoryList"
value-name="dictKey"
label-name="dictValue"
@confirm="e => onConfirm(e, 'category')"
safe-area-inset-bottom
></u-select>
<!-- 通知日期 -->
<u-picker
mode="time"
v-model="timeShow"
:params="timeParams"
start-year="2000"
end-year="2050"
@confirm="e => onConfirm(e, 'releaseTime')"
safe-area-inset-bottom
></u-picker>
</view>
</template>
<script>
import riderCurd from '@/components/rider-crud/index'
import mpHtml from '@/components/rider-crud/components/mp-html/mp-html'
import { getList, add, update, remove } from '@/api/notice.js'
import { dictionary } from '@/api/system/dict.js'
import { logo } from '../../common/setting'
import { getDicLabel } from '@/components/rider-crud/util/tool.js'
export default {
components: { riderCurd, mpHtml },
data() {
return {
loadStatus: 'loadmore', //
checkedSwitch: false, //
form: { content: '' }, //
formLabel: {}, // label
list: [], //
params: {
//
current: 1,
size: 10
},
readonly: false, //
option: {
//
navTitle: '通知公告', //
manageBtn: true, //
searchShow: true, //
searchShow: true, //
labelWidth: 160, //formlable 160rpx
column: [
{
label: '通知标题',
prop: 'title'
},
{
label: '通知类型',
prop: 'categoryName'
},
{
label: '通知日期',
prop: 'releaseTime'
}
],
actions: [
//
{
name: '编辑', //
icon: 'edit-pen', //
color: '#333', //
fontsize: 30, //rpx
width: 40, // px
background: '#fff' //
},
{
name: '删除',
icon: 'trash',
color: '#333',
fontsize: 30,
width: 40,
background: '#fff'
}
]
},
rules: {
//
title: [{ required: true, message: '请输入通知标题', trigger: 'blur' }],
category: [{ required: true, message: '请选择通知类型', trigger: 'change', type: 'number' }],
releaseTime: [{ required: true, message: '请选择通知日期', trigger: 'change' }]
},
timeShow: false,
timeParams: {
year: true,
month: true,
day: true,
hour: true,
minute: true,
second: true
},
categoryShow: false,
categoryList: [] //
}
},
onShow() {
let that = this
uni.$on('richBack', function(data) {
that.$set(that.form, 'content', data)
})
},
onReady() {
this.getList(true)
this.getCategoryList()
},
onPullDownRefresh() {
this.getList(true)
},
onReachBottom() {
if (this.loadStatus == 'nomore') return
this.getList()
},
methods: {
//
getList(override = false) {
if (override) {
this.params.current = 1
}
const { size } = this.params
getList(this.params)
.then(res => {
const { records, current } = res.data
if (records.length < size) this.loadStatus = 'nomore'
else this.loadStatus = 'loadmore'
if (override) {
this.list = records
} else {
this.list = this.list.concat(records)
}
this.params.current++
uni.stopPullDownRefresh()
})
.catch(err => {
uni.stopPullDownRefresh()
})
},
//
getCategoryList() {
dictionary({ code: 'notice' }).then(res => {
this.categoryList = res.data
})
},
//
onConfirm(e, type) {
if (type == 'category') {
this.formLabel[type] = e[0].label
this.form.category = Number(e[0].value)
} else {
this.$set(this.form, 'releaseTime', e.year + '-' + e.month + '-' + e.day + ' ' + e.hour + ':' + e.minute + ':' + e.second)
}
},
//
onRichEdit(e) {
uni.navigateTo({
url: `/pages/rich/index?data=${e}`
})
},
// ()
onSearch(val) {
this.params = {
current: 1,
size: 10,
title: val
}
this.getList(true)
},
//
onActionClick({ index, data, action, done }) {
const { name } = action
switch (name) {
case '新增':
this.formLabel = {}
this.form = { content: '' }
done()
break
case '删除':
this.onRemove(data.id)
break
case '查看':
this.readonly = true
case '编辑':
this.formLabel.category = getDicLabel(data.category, this.categoryList)
done()
break
}
},
//
onRemove(id) {
uni.showModal({
title: '确定要删除吗?',
success: action => {
if (action.confirm) {
remove(id).then(() => {
uni.showToast({
title: '删除成功',
icon: 'none'
})
setTimeout(() => {
this.$refs['crud'].allCheckHide(false)
}, 100)
// #ifdef APP
this.getList(true)
// #endif
// #ifndef APP
uni.startPullDownRefresh({})
// #endif
})
}
}
})
},
//
onSubmit(form, action, unloading, done) {
if (action == '新增') {
add(form)
.then(res => {
uni.showToast({
title: '新增成功',
icon: 'none'
})
this.formLabel = {}
done()
uni.startPullDownRefresh({})
})
.catch(() => {
unloading()
})
} else if (action == '编辑') {
update(form)
.then(res => {
uni.showToast({
title: '编辑成功',
icon: 'none'
})
this.formLabel = {}
done()
uni.startPullDownRefresh({})
})
.catch(() => {
unloading()
})
}
}
}
}
</script>
<style>
page {
background: #f6f6f6;
}
</style>

311
pages/manage/post.vue Normal file
View File

@ -0,0 +1,311 @@
<template>
<view class="container">
<view class="safe-area-inset-bottom">
<rider-curd
v-model="form"
:option="option"
:data="list"
:rules="rules"
:readonly.sync="readonly"
@search="onSearch"
@action-click="onActionClick"
@submit="onSubmit"
@del="onRemove"
ref="crud"
>
<template #form>
<u-form-item label="岗位类型" prop="category" :label-width="option.labelWidth || 160" required>
<u-input
v-model="formLabel.category"
placeholder="角色名称"
type="select"
@click="
() => {
if (!readonly) categoryShow = true
}
"
/>
</u-form-item>
<u-form-item label="岗位编号" prop="postCode" :label-width="option.labelWidth || 160" required>
<u-input v-model="form.postCode" :disabled="readonly" placeholder="岗位编号" />
</u-form-item>
<u-form-item label="岗位名称" prop="postName" :label-width="option.labelWidth || 160" required>
<u-input v-model="form.postName" :disabled="readonly" placeholder="岗位名称" />
</u-form-item>
<u-form-item label="岗位排序" prop="sort" :label-width="option.labelWidth || 160" required>
<u-input v-model="form.sort" :disabled="readonly" type="number" placeholder="岗位排序" />
</u-form-item>
<u-form-item label="岗位描述" prop="remark" :label-width="option.labelWidth || 160">
<u-input
v-model="form.remark"
:disabled="readonly"
type="textarea"
:maxlength="-1"
:height="35"
placeholder="岗位描述"
/>
</u-form-item>
</template>
<template #loadmore>
<u-loadmore :status="loadStatus" @loadmore="getList()" />
</template>
</rider-curd>
</view>
<u-select
v-model="categoryShow"
:list="categoryList"
value-name="dictKey"
label-name="dictValue"
@confirm="onConfirm"
safe-area-inset-bottom
></u-select>
</view>
</template>
<script>
import riderCurd from '@/components/rider-crud/index'
import { getList, add, update, remove } from '@/api/system/post.js'
import { tenantSelect } from '@/api/system/tenant.js'
import { dictionary } from '@/api/system/dict.js'
import { getDicLabel } from '@/components/rider-crud/util/tool.js'
export default {
components: { riderCurd },
data() {
return {
checkedSwitch: false,
form: {},
formLabel: {},
list: [],
params: {
current: 1,
size: 10
},
readonly: false, //
loadStatus: 'loadmore', //
option: {
navTitle: '岗位管理', //
manageBtn: true, //
searchShow: true, //
labelWidth: 160, //formlable 160rpx
column: [
{
label: '岗位名称',
prop: 'postName'
},
{
label: '岗位编号',
prop: 'postCode'
},
{
label: '所属租户',
prop: 'tenantName'
},
{
label: '岗位类型',
prop: 'categoryName'
},
{
label: '岗位排序',
prop: 'sort'
},
{
label: '岗位描述',
prop: 'remark'
}
],
actions: [
{
name: '编辑', //
icon: 'edit-pen', //
color: '#333', //
fontsize: 30, //rpx
width: 40, // px
background: '#fff' //
},
{
name: '删除',
icon: 'trash',
color: '#333',
fontsize: 30,
width: 40,
background: '#fff'
}
]
},
rules: {
category: [{ required: true, message: '请选择岗位类型', trigger: 'change', type: 'number' }],
postCode: [{ required: true, message: '请输入岗位编号', trigger: 'blur' }],
postName: [{ required: true, message: '请输入岗位名称', trigger: 'blur' }],
sort: [{ required: true, message: '请输入岗位排序', trigger: 'blur', type: 'number' }]
},
categoryShow: false,
categoryList: [],
category: '',
tenantList: []
}
},
onReady() {
this.getTenant().then(() => {
this.getList(true)
})
this.getCategoryList()
},
onPullDownRefresh() {
this.getList(true)
},
onReachBottom() {
if (this.loadStatus == 'nomore') return
this.getList()
},
methods: {
//
getList(override = false) {
if (override) {
this.params.current = 1
}
const { size } = this.params
getList(this.params)
.then(res => {
const { records, current } = res.data
if (records.length < size) this.loadStatus = 'nomore'
else this.loadStatus = 'loadmore'
if (override) {
this.list = records
} else {
this.list = this.list.concat(records)
}
if (this.list && this.list.length > 0) {
this.list.map(e => {
e.tenantName = getDicLabel(e.tenantId, this.tenantList, { label: 'tenantName', value: 'tenantId' })
})
}
this.params.current++
uni.stopPullDownRefresh()
})
.catch(err => {
uni.stopPullDownRefresh()
})
},
//
getCategoryList() {
dictionary({ code: 'post_category' }).then(res => {
this.categoryList = res.data
})
},
//
getTenant() {
return new Promise(resolve => {
tenantSelect().then(res => {
this.tenantList = res.data
resolve()
})
})
},
//
onConfirm(e) {
this.$set(this.form, 'category', Number(e[0].value))
this.$set(this.formLabel, 'category', e[0].label)
},
// ()
onSearch(val) {
this.params = {
current: 1,
size: 10,
postName: val
}
this.getList(true)
},
//
onActionClick({ index, data, action, done }) {
const { name } = action
console.log(name, index)
switch (name) {
case '新增':
done()
break
case '删除':
this.onRemove(data.id)
break
case '查看':
this.readonly = true
case '编辑':
this.formLabel.category = getDicLabel(data.category, this.categoryList)
done()
break
}
},
//
onRemove(id) {
uni.showModal({
title: '确定要删除吗?',
success: action => {
if (action.confirm) {
remove(id).then(() => {
uni.showToast({
title: '删除成功',
icon: 'none'
})
setTimeout(() => {
this.$refs['crud'].allCheckHide(false)
}, 100)
// #ifdef APP
this.getList(true)
// #endif
// #ifndef APP
uni.startPullDownRefresh({})
// #endif
})
}
}
})
},
//
onSubmit(form, action, unloading, done) {
if (action == '新增') {
add(form)
.then(res => {
uni.showToast({
title: '新增成功',
icon: 'none'
})
this.formLabel = {}
done()
uni.startPullDownRefresh({})
})
.catch(() => {
unloading()
})
} else if (action == '编辑') {
update(form)
.then(res => {
uni.showToast({
title: '编辑成功',
icon: 'none'
})
this.formLabel = {}
done()
uni.startPullDownRefresh({})
})
.catch(() => {
unloading()
})
}
}
}
}
</script>
<style>
page {
background: #f6f6f6;
}
</style>
<style lang="scss" scoped>
.nav-right {
color: #fff;
font-size: 28rpx;
font-weight: normal;
padding-right: 24rpx;
}
</style>

372
pages/manage/role.vue Normal file
View File

@ -0,0 +1,372 @@
<template>
<view class="container">
<view class="safe-area-inset-bottom">
<rider-curd
v-model="form"
:option="option"
:data="list"
:rules="rules"
:readonly.sync="readonly"
@search="onSearch"
@action-click="onActionClick"
@submit="onSubmit"
@del="onRemove"
ref="crud"
>
<template #form>
<u-form-item label="角色名称" prop="roleName" :label-width="option.labelWidth || 160" required>
<u-input v-model="form.roleName" :disabled="readonly" placeholder="角色名称" />
</u-form-item>
<u-form-item label="上级角色" prop="parentId" :label-width="option.labelWidth || 160">
<u-input
v-model="formLabel.parentName"
placeholder="上级角色"
type="select"
@click="
() => {
if (!readonly) $refs.treePicker._show()
}
"
/>
</u-form-item>
<u-form-item label="角色别名" prop="roleAlias" required :label-width="option.labelWidth || 160">
<u-input v-model="form.roleAlias" :disabled="readonly" placeholder="角色别名" />
</u-form-item>
<u-form-item label="角色排序" prop="sort" required :label-width="option.labelWidth || 160">
<u-input v-model="form.sort" :disabled="readonly" type="number" placeholder="角色排序" />
</u-form-item>
</template>
<template #loadmore>
<u-loadmore :status="loadStatus" @loadmore="getList()" />
</template>
</rider-curd>
</view>
<!-- 上级角色tree -->
<rider-tree
ref="treePicker"
:range="roleTreeList"
@cancel="$refs.treePicker._hide()"
@confirm="onTreeConfirm"
idKey="id"
nameKey="title"
:multiple="false"
:cascade="false"
:foldAll="false"
confirmColor="#4E7FF9"
cancelColor="#757575"
title="上级角色"
titleColor="#757575"
></rider-tree>
<!-- 菜单权限 -->
<rider-tree
ref="menuPicker"
:range="menuTreeList"
@cancel="$refs.menuPicker._hide()"
@confirm="onMenuConfirm"
idKey="id"
nameKey="title"
:multiple="true"
:cascade="true"
:foldAll="true"
confirmColor="#4E7FF9"
cancelColor="#757575"
title="菜单权限"
titleColor="#757575"
></rider-tree>
</view>
</template>
<script>
import riderCurd from '@/components/rider-crud/index'
import riderTree from '@/components/rider-crud/components/tree-picker'
import { getList, add, update, remove, roleTree, menuTree, menuTreeKey, grant } from '@/api/system/role.js'
import { tenantSelect } from '@/api/system/tenant.js'
import { getDicLabel } from '@/components/rider-crud/util/tool.js'
export default {
components: { riderCurd, riderTree },
data() {
return {
checkedSwitch: false,
form: {},
formLabel: {},
list: [],
params: {
current: 1,
size: 10
},
readonly: false, //
loadStatus: 'loadmore', //
option: {
navTitle: '角色管理', //
manageBtn: true, //
searchShow: true, //
labelWidth: 160, //formlable 160rpx
column: [
{
label: '角色名称',
prop: 'roleName'
},
{
label: '所属租户',
prop: 'tenantName'
},
{
label: '角色别名',
prop: 'roleAlias'
},
{
label: '角色排序',
prop: 'sort'
}
],
actions: [
{
name: '编辑', //
icon: 'edit-pen', //
color: '#333', //
fontsize: 30, //rpx
width: 40, // px
background: '#fff' //
},
{
name: '删除',
icon: 'trash',
color: '#333',
fontsize: 30,
width: 40,
background: '#fff'
},
{
name: '菜单权限',
icon: 'setting',
color: '#333',
fontsize: 30,
width: 40,
background: '#fff'
}
]
},
rules: {
roleName: [{ required: true, message: '请输入角色名称', trigger: 'blur' }],
roleAlias: [{ required: true, message: '请输入角色别名', trigger: 'blur' }],
sort: [{ required: true, message: '请输入角色排序', trigger: 'blur', type: 'number' }]
},
tenantList: [],
roleTreeList: [],
menuTreeList: [],
menuCheckIds: [],
roleIds: null
}
},
onReady() {
this.getTenant().then(() => {
this.getList(true)
})
},
onPullDownRefresh() {
this.getList(true)
},
onReachBottom() {
if (this.loadStatus == 'nomore') return
this.getList()
},
methods: {
//
getList(override = false) {
if (override) {
this.params.current = 1
}
const { size } = this.params
getList(this.params)
.then(res => {
const records = res.data
if (records.length < size) this.loadStatus = 'nomore'
else this.loadStatus = 'loadmore'
if (override) {
this.list = records
} else {
this.list = this.list.concat(records)
}
if (this.list && this.list.length > 0) {
this.list = this.filterList(this.list)
}
this.params.current++
uni.stopPullDownRefresh()
})
.catch(err => {
uni.stopPullDownRefresh()
})
},
filterList(list) {
if (!list || list.length == 0) return list
list.forEach(e => {
e.tenantName = getDicLabel(e.tenantId, this.tenantList, { label: 'tenantName', value: 'tenantId' })
if (e.children) this.filterList(e.children)
})
return list
},
//
getTenant() {
return new Promise(resolve => {
roleTree().then(res => {
this.roleTreeList = res.data
})
tenantSelect().then(res => {
this.tenantList = res.data
resolve()
})
})
},
//
getMenu(roleIds) {
menuTreeKey({ roleIds }).then(res => {
this.menuCheckIds = res.data.menu
})
menuTree().then(res => {
this.recursion(this.menuCheckIds, res.data.menu)
this.menuTreeList = res.data.menu
setTimeout(() => {
this.$refs.menuPicker._show()
}, 10)
})
},
//
recursion(arr, menu) {
menu.forEach(e => {
if (arr.includes(e.id)) e.checked = true
if (e.hasChildren || (e.children && e.children.length > 0)) {
this.recursion(arr, e.children)
}
})
},
//
onMenuConfirm(e) {
let arr = []
e.forEach(e => {
arr.push(e.id)
})
const params = {
apiScopeIds: [],
dataScopeIds: [],
roleIds: [this.roleIds],
menuIds: arr
}
grant(params).then(res => {
uni.showToast({
icon: 'none',
title: '操作成功'
})
})
},
//
onTreeConfirm(e) {
this.$set(this.form, 'parentId', e[0].id)
this.$set(this.formLabel, 'parentName', e[0].name)
},
// ()
onSearch(val) {
this.params = {
current: 1,
size: 10,
roleName: val
}
this.getList(true)
},
//
onActionClick({ index, data, action, done }) {
const { name } = action
console.log(name, index)
switch (name) {
case '新增':
done()
break
case '删除':
this.onRemove(data.id)
break
case '查看':
this.readonly = true
case '编辑':
this.$set(this.formLabel, 'parentName', data.parentName)
done()
break
case '菜单权限':
this.roleIds = data.id
this.getMenu(data.id)
break
}
if (['删除', '编辑'].includes(name)) this.$refs['crud'].hideChildren()
},
//
onRemove(id) {
uni.showModal({
title: '确定要删除吗?',
success: action => {
if (action.confirm) {
remove(id).then(() => {
uni.showToast({
title: '删除成功',
icon: 'none'
})
setTimeout(() => {
this.$refs['crud'].allCheckHide(false)
}, 100)
// #ifdef APP
this.getList(true)
// #endif
// #ifndef APP
uni.startPullDownRefresh({})
// #endif
})
}
}
})
},
//
onSubmit(form, action, unloading, done) {
if (action == '新增') {
add(form)
.then(res => {
uni.showToast({
title: '新增成功',
icon: 'none'
})
// this.category = ''
done()
uni.startPullDownRefresh({})
})
.catch(() => {
unloading()
})
} else if (action == '编辑') {
update(form)
.then(res => {
uni.showToast({
title: '编辑成功',
icon: 'none'
})
// this.category = ''
done()
uni.startPullDownRefresh({})
})
.catch(() => {
unloading()
})
}
}
}
}
</script>
<style>
page {
background: #f6f6f6;
}
</style>
<style lang="scss" scoped>
.nav-right {
color: #fff;
font-size: 28rpx;
font-weight: normal;
padding-right: 24rpx;
}
</style>

260
pages/myReserve/detail.vue Normal file
View File

@ -0,0 +1,260 @@
<template>
<view class="container">
<!-- 蓝色头部信息区 -->
<view class="header-section">
<view class="serial-no">流水号{{orderInfo.code}}</view>
<view class="time-info">{{orderInfo.bookingTime}}</view>
<view class="project-info"> {{orderInfo.itemName}} </view>
</view>
<!-- 取号信息卡片 -->
<view class="info-card new-card" v-if="isTicketInfoShow">
<view class="ticket_status">无感取号成功</view>
<view class="info-row">
<text class="info-label">排队号</text>
<text class="info-value">{{ticketInfo.queueNum}}</text>
</view>
<view class="info-row">
<text class="info-label">窗口号</text>
<text class="info-value">{{ticketInfo.counterlist}}</text>
</view>
</view>
<!-- 详情信息卡片 -->
<view class="info-card">
<view class="info-row">
<text class="info-label">预约大厅</text>
<text class="info-value">广西区直住房保障中心</text>
</view>
<view class="info-row">
<text class="info-label">预约部门</text>
<text class="info-value">{{orderInfo.deptName}}</text>
</view>
</view>
<view class="info-card">
<view class="info-row">
<text class="info-label">姓名</text>
<text class="info-value">{{ userinfo.name }}</text>
</view>
<view class="info-row">
<text class="info-label">手机号</text>
<text class="info-value">{{ userinfo.phone }}</text>
</view>
<view class="info-row">
<text class="info-label">证件类型</text>
<text class="info-value">身份证</text>
</view>
<view class="info-row">
<text class="info-label">证件号码</text>
<text class="info-value">{{ userinfo.idCard }}</text>
</view>
</view>
<!-- 取消预约按钮 -->
<view class="btn-wrap">
<button class="btn-cancel" v-if="orderInfo.status==1" @click="handleCancel">取消预约</button>
</view>
</view>
</template>
<script>
import {
getDetail,
getUserInfo,
cancel,
getIsSuccessTicket
} from '@/api/reserve.js'
export default {
data() {
return {
//
isTicketInfoShow: false,
ticketInfo: {},
orderInfo: {},
userinfo: {},
id: '',
}
},
onLoad(options) {
this.id = options.id
this.getTicketInfo()
this.getUserInfo()
this.getDetail()
},
methods: {
//
getTicketInfo() {
getIsSuccessTicket({
yyNo: this.id
}).then(res => {
if (res.code) {
this.isTicketInfoShow = Object.keys(res.data).length > 0 ? true : false
this.ticketInfo = res.data
}
})
},
//
getDetail() {
getDetail({
id: this.id
}).then(res => {
this.orderInfo = res.data
})
},
//
getUserInfo() {
getUserInfo().then(res => {
this.userinfo = res.data
})
},
//
handleGoBack() {
uni.navigateBack()
},
//
handleCancel() {
uni.showModal({
title: '提示',
content: '确定要取消该预约吗?',
success: (res) => {
if (res.confirm) {
cancel({
id: this.orderInfo.id
}).then(res => {
if (res.code == 200) {
uni.showToast({
title: '取消成功',
icon: 'success'
})
setTimeout(() => {
uni.navigateBack({
delta: 1 //
})
}, 2000)
}
})
}
}
})
}
}
}
</script>
<style scoped>
* {
box-sizing: border-box;
}
/* 全局容器 */
.container {
min-height: 100vh;
background-color: #f5f5f5;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
display: flex;
flex-direction: column;
}
/* 蓝色头部信息区 */
.header-section {
width: 100%;
background: linear-gradient(to right, rgba(75, 112, 252, 1), rgba(67, 195, 252, 1));
padding: 40rpx 30rpx;
box-sizing: border-box;
}
.serial-no {
font-size: 32rpx;
color: #ffffff;
text-align: center;
margin-bottom: 30rpx;
opacity: 0.9;
}
.time-info {
font-size: 38rpx;
font-weight: 500;
color: #ffffff;
margin-bottom: 15rpx;
line-height: 1.4;
}
.project-info {
font-size: 32rpx;
color: #ffffff;
line-height: 1.5;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 16rpx;
padding: 20rpx;
}
/* 信息卡片 */
.info-card {
background-color: #ffffff;
border-radius: 24rpx;
margin: 20rpx 30rpx;
padding: 0 30rpx;
}
.new-card {
padding-top: 20rpx;
}
.ticket_status {
margin: 0 auto;
padding: 10rpx 32rpx;
width: fit-content;
font-size: 32rpx;
font-weight: bold;
color: rgba(3, 195, 106, 1);
background-color: rgba(220, 255, 239, 1);
border-radius: 10rpx;
}
.info-row {
display: flex;
align-items: flex-start;
padding: 20rpx 0;
}
.info-label {
width: 220rpx;
font-size: 34rpx;
font-weight: 500;
color: #333333;
flex-shrink: 0;
}
.info-value {
flex: 1;
font-size: 34rpx;
color: #333333;
text-align: right;
line-height: 1.5;
}
/* 取消预约按钮 */
.btn-wrap {
padding: 20rpx 30rpx 40rpx;
}
.btn-cancel {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background-color: rgba(255, 239, 239, 1);
color: rgba(244, 54, 54, 1);
border-radius: 44rpx;
font-size: 34rpx;
font-weight: 500;
border: 2rpx solid rgba(255, 195, 195, 1);
}
</style>

204
pages/myReserve/index.vue Normal file
View File

@ -0,0 +1,204 @@
<template>
<view class="container">
<!-- 预约列表 -->
<view class="list-container">
<!-- 预约成功状态 -->
<view class="reserve-item" v-for="(item, index) in successList" :key="`success-${index}`">
<view class="item-header">
<text class="item-time">{{ item.bookingTime }}</text>
<text
:class="['status', item.status == 1 ? 'success' : 'canceled']">{{ item.status == 1 ? '预约成功' : '已取消' }}</text>
</view>
<view class="item-content">
<text class="item-project">{{ item.itemName }}</text>
</view>
<view class="item-btns">
<button class="btn-cancel" v-if="item.status == 1" @click="handleCancel(item)">取消预约</button>
<button class="btn-cancel" v-if="item.status == 4">{{getStatus(item.status)}}</button>
<button class="btn-detail" @click="handleDetail(item)">查看详情</button>
</view>
</view>
<!-- 空状态 -->
<view class="empty-tip" v-if="successList.length === 0"> 暂无预约记录 </view>
</view>
</view>
</template>
<script>
import {
getUserInfo,
myReserve,
cancel
} from '@/api/reserve.js'
export default {
data() {
return {
//
successList: [],
userinfo: {},
statusList: [],
}
},
onLoad() {
this.getUserInfo()
this.statusList = uni.getStorageSync('statusList');
},
methods: {
getStatus(code){
return this.statusList.find(item=>item.dictKey == code)?.dictValue
},
getUserInfo() {
getUserInfo().then(res => {
this.userinfo = res.data
this.getList()
})
},
getList() {
let params = {
size: 10,
current: 1,
idCard: this.userinfo.idCard,
descs: 'update_time'
}
myReserve(params).then(res => {
this.successList = res.data.records
})
},
//
handleGoBack() {
uni.navigateBack()
},
//
handleCancel(item) {
uni.showModal({
title: '提示',
content: '确定要取消该预约吗?',
success: res => {
if (res.confirm) {
cancel({
id: item.id
}).then(res => {
if (res.code == 200) {
this.getList()
}
})
}
}
})
},
//
handleDetail(item) {
uni.navigateTo({
url: `/pages/myReserve/detail?id=${item.id}`
})
}
}
}
</script>
<style scoped>
/* 全局容器 */
.container {
min-height: 100vh;
background-color: #f5f5f5;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
display: flex;
flex-direction: column;
}
/* 列表容器 */
.list-container {
flex: 1;
padding: 20rpx 30rpx;
}
/* 预约项卡片 */
.reserve-item {
background-color: #ffffff;
border-radius: 24rpx;
padding: 30rpx;
margin-bottom: 20rpx;
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.item-time {
font-size: 34rpx;
font-weight: 500;
color: #333333;
}
.status {
font-size: 30rpx;
font-weight: 500;
padding: 6rpx 16rpx;
border-radius: 8rpx;
}
.status.success {
background-color: #e6f9e6;
color: #00b578;
}
.status.canceled {
background-color: #f0f0f0;
color: #999999;
}
.item-content {
margin-bottom: 30rpx;
}
.item-project {
font-size: 32rpx;
color: #333333;
line-height: 1.5;
}
.item-btns {
display: flex;
justify-content: flex-end;
gap: 20rpx;
}
.btn-cancel {
margin: 0;
width: 200rpx;
height: 70rpx;
line-height: 70rpx;
background-color: rgba(255, 239, 239, 1);
color: rgba(244, 54, 54, 1);
border-radius: 35rpx;
font-size: 32rpx;
font-weight: 500;
border: 2rpx solid rgba(255, 195, 195, 1);
}
.btn-detail {
margin: 0;
width: 200rpx;
height: 70rpx;
line-height: 70rpx;
background-color: rgba(242, 247, 255, 1);
color: rgba(49, 134, 255, 1);
border-radius: 35rpx;
font-size: 32rpx;
font-weight: 500;
border: 2rpx solid rgba(208, 227, 255, 1);
}
/* 空状态提示 */
.empty-tip {
text-align: center;
padding: 100rpx 0;
font-size: 28rpx;
color: #999999;
}
</style>

290
pages/news/detail.vue Normal file
View File

@ -0,0 +1,290 @@
<template>
<view class="container">
<u-navbar :is-fixed="false" :border-bottom="false" :is-back="true" back-icon-name="arrow-leftward" title="新闻详情页"
:background="{ background: '#fff' }" title-color="#000000">
<view class="right-nav" slot="right">
<image src="/static/images/news/search.png" class="icon icon-search" mode="widthFix"></image>
<image src="/static/images/news/menu.png" class="icon icon-menu" mode="widthFix"></image>
</view>
</u-navbar>
<view class="header">
<image src="" class="avatar" mode=""></image>
<view class="info">
<view class="name">
中国空间站
<text class="tag">专栏作家</text>
</view>
<view class="date">11-12</view>
</view>
<view class="btn">+关注</view>
</view>
<view class="content">
<view class="title">
天舟五号创造多个航天新纪录
</view>
<view class="static">
<view class="left">
<view class="block">
<image src="/static/images/news/gf.png" class="gf"></image>
官方
</view>
<view class="block">
<image src="/static/images/news/x.png" class="gf"></image>
焦点新闻
</view>
</view>
<view class="right">
<image src="/static/images/news/l.png" class="l" mode="widthFix"></image>
66666
</view>
</view>
<view class="detail">
<u-parse :html="content"></u-parse>
</view>
</view>
<view class="footer">
<input type="text" value="" placeholder="说点什么..." class="ipt" />
<view class="control">
<view class="block">
<image src="/static/images/news/c.png" class="icon c" mode="widthFix"></image>
458
</view>
<view class="block">
<image src="/static/images/news/s.png" class="icon s" mode="widthFix"></image>
</view>
<view class="block">
<image src="/static/images/news/t.png" class="icon t" mode="widthFix"></image>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
content: `<p>在北京航天飞行控制中心拍摄的天舟五号货运飞船与空间站组合体完成自主快速交会对接示意图。孙丰晓摄(新华社发)</p>
<p>北京时间2022年11月12日10时03分在文昌航天发射场搭载天舟五号货运飞船的长征七号遥六运载火箭点火发射将天舟五号货运飞船送入预定轨道随后天舟五号货运飞船与在轨运行的空间站组合体进行自主快速交会对接中国航天员首次在空间站迎接货运飞船来访</p>
<p>朝着在距离地面约400公里轨道上运行的中国空间站组合体天舟五号首次实现2小时自主快速交会对接创造世界纪录这一技术突破对于提升我国空间交会对接水平提升空间站任务应急物资补给能力具有重要意义</p>
<p>空间站阶段最快交会对接让人类航天器交会对接用时进入2小时时代</p>
<p>此次任务是中国空间站T字基本构型在轨组装完成后太空快递首次送达执行运输任务的仍是长七火箭</p>`
};
}
};
</script>
<style lang="scss">
.container {
padding: 0 0 150rpx;
}
.right-nav {
display: flex;
align-items: center;
margin-right: 40rpx;
.icon {
height: auto;
&-search {
width: 40rpx;
margin-right: 40rpx;
}
&-menu {
width: 40rpx;
}
}
}
.header {
display: flex;
align-items: center;
padding: 44rpx;
.avatar {
flex-shrink: 0;
width: 69rpx;
height: 69rpx;
background: #ffffff;
border: 1px solid #69b0ff;
border-radius: 50%;
}
.info {
flex: 1;
margin-left: 14rpx;
.name {
display: flex;
align-items: center;
font-size: 28rpx;
font-family: PingFang SC;
font-weight: bold;
color: #000000;
line-height: 36rpx;
.tag {
margin-left: 10rpx;
display: inline-block;
padding: 8rpx 10rpx;
font-size: 14rpx;
font-family: PingFang SC;
font-weight: 500;
color: #4cbeca;
line-height: 21rpx;
border: 1px solid #0bb9c8;
border-radius: 6px;
}
}
.date {
font-size: 24rpx;
font-family: PingFang SC;
font-weight: 500;
color: #a3a3a3;
line-height: 36rpx;
}
}
.btn {
padding: 8rpx 19rpx;
background: #e6e6e6;
border-radius: 6rpx;
font-size: 20rpx;
font-family: PingFang SC;
font-weight: bold;
color: #4cbeca;
line-height: 1;
}
}
.content {
padding: 0 44rpx;
.title {
font-size: 40rpx;
font-family: PingFang SC;
font-weight: bold;
color: #000000;
line-height: 47rpx;
}
.static {
margin-top: 34rpx;
display: flex;
justify-content: space-between;
align-items: center;
.left,
.right {
display: flex;
align-items: center;
.block {
display: flex;
align-items: center;
margin-right: 10rpx;
}
}
.left {
font-size: 20rpx;
font-family: PingFang SC;
font-weight: bold;
color: #787878;
line-height: 1;
.gf {
width: 24rpx;
height: 24rpx;
margin-right: 8rpx;
}
}
.right {
font-size: 20rpx;
font-family: PingFang SC;
font-weight: bold;
color: #787878;
line-height: 1;
.l {
width: 25rpx;
height: auto;
margin-right: 6rpx;
}
}
}
.detail {
font-size: 24rpx;
font-family: PingFang SC;
font-weight: 500;
color: #000000;
line-height: 47rpx;
margin-top: 50rpx;
}
}
.footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 999;
height: 100rpx;
padding: 20rpx;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
background-color: #ffffff;
.ipt {
width: 380rpx;
height: 77rpx;
background: #f7f7f7;
border-radius: 38px;
padding: 0 37rpx;
box-sizing: border-box;
margin-right: 20rpx;
}
.control {
flex: 1;
display: flex;
align-items: center;
justify-content: space-around;
.block {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
}
.icon {
height: auto;
}
.c {
width: 41rpx;
margin-right: 10rpx;
}
.s {
width: 36rpx;
}
.t {
width: 31rpx;
}
}
}
</style>

259
pages/news/list.vue Normal file
View File

@ -0,0 +1,259 @@
<template>
<view class="container">
<u-navbar :is-fixed="false" :border-bottom="false" :is-back="true" back-icon-name="arrow-leftward"
back-icon-color="#fff" back-icon-size="35" title="新闻动态" :background="{ background: 'linear-gradient(90deg, #69b0ff, #5f88ff)' }"
title-color="#fff">
<image slot="right" src="/static/images/home/message.png" class="message-icon" mode="widthFix"></image>
</u-navbar>
<view class="tab">
<u-tabs :list="tabs" :is-scroll="false" :current="current" active-color="#69b0ff" inactive-color="#595959"
height="100" @change="change"></u-tabs>
</view>
<swiper id="swiperBox" :style="{ height: swiperHeight + 'px' }" :current="current" @change="tabsChange">
<swiper-item class="swiper-item" v-for="(pitem, pindex) in tabs" :key="pindex">
<scroll-view scroll-y style="width: 100%;height: 100%;" refresher-enabled
:refresher-triggered="pitem.triggered" @scrolltolower="onreachBottom"
@refresherrefresh="refresherrefresh(pindex)" @refresherrestore="refresherrestore(pindex)"
@refresherabort="refresherabort(pindex)">
<view class="content">
<navigator class="news" v-for="(item, index) in contentList[pindex].list" :key="index"
url="/pages/news/detail" hover-class="none">
<view class="head">
<image src="" class="avatar" mode=""></image>
<view class="info">
<view class="name">
中国空间站
<text class="tag">专栏作家</text>
</view>
<view class="date">11-12</view>
</view>
<u-icon name="arrow-right" size="35" color="#C5C5C5"></u-icon>
</view>
<view class="des">
天舟五号创造多个航天新纪录
</view>
</navigator>
<view class="nomore">
<u-loadmore :status="contentList[pindex].status" />
</view>
</view>
</scroll-view>
</swiper-item>
</swiper>
</view>
</template>
<script>
export default {
data() {
return {
swiperHeight: 0,
tabs: [{
name: '互动',
triggered: false,
_triggered: false
},
{
name: '动态',
triggered: false,
_triggered: false
},
{
name: '通知',
triggered: false,
_triggered: false
}
],
contentList: [{
list: 8,
page: 1,
status: 'loadmore'
},
{
list: 8,
page: 1,
status: 'loadmore'
},
{
list: 8,
page: 1,
status: 'loadmore'
}
],
current: 0
};
},
onLoad() {},
onReady() {
let that = this;
uni.getSystemInfo({
success(e) {
console.log(e);
let {
windowWidth,
windowHeight,
safeArea
} = e;
const query = uni.createSelectorQuery().in(that);
query
.select('#swiperBox')
.boundingClientRect(data => {
that.swiperHeight = safeArea.bottom - data.top;
})
.exec();
}
});
},
onPullDownRefresh() {
setTimeout(() => {
uni.stopPullDownRefresh();
}, 1500);
},
methods: {
tabsChange(e) {
this.current = e.detail.current;
},
refresherrefresh(index) {
console.log('自定义下拉刷新被触发');
let _this = this;
if (_this.tabs[index]._triggered) {
return;
}
_this.tabs[index]._triggered = true;
//triggeredtruetrue
if (!_this.tabs[index].triggered) {
_this.tabs[index].triggered = true;
}
setTimeout(() => {
_this.tabs[index].triggered = false; //onRestore
_this.tabs[index]._triggered = false;
}, 10);
},
refresherrestore(index) {
console.log('自定义下拉刷新被复位');
let _this = this;
_this.tabs[index].triggered = false;
_this.tabs[index]._triggered = false;
},
refresherabort(index) {
console.log('自定义下拉刷新被中止 ');
let _this = this;
_this.tabs[index].triggered = false;
_this.tabs[index]._triggered = false;
},
// scroll-view
onreachBottom() {
let current = this.current;
if (this.contentList[current].page >= 3) return;
this.contentList[current].status = 'loading';
setTimeout(() => {
this.contentList[current].page = ++this.contentList[current].page;
this.contentList[current].list += 10;
if (this.contentList[current].page >= 3)
this.contentList[current].status = 'nomore';
else this.contentList[current].status = 'loading';
}, 2000);
},
change(index) {
this.current = index;
}
}
};
</script>
<style lang="scss">
.message-icon {
width: 32rpx;
height: auto;
margin-right: 27rpx;
}
.tab {
margin: 0 0 10rpx;
border-bottom: 1px solid #e4e7ed;
}
.content {
padding: 0 0 150rpx;
.news {
&:not(:last-of-type) {
border-bottom: 2rpx solid #e4e7ed;
}
}
}
.nomore {
padding: 30rpx 20rpx;
}
.news {
margin: 30rpx 30rpx 0;
padding: 0 0 30rpx;
.head {
display: flex;
.avatar {
flex-shrink: 0;
width: 69rpx;
height: 69rpx;
background: #ffffff;
// border: 1px solid #14b9c8;
border: 1px solid #5f88ff;
border-radius: 50%;
}
.info {
flex: 1;
margin-left: 14rpx;
.name {
display: flex;
align-items: center;
font-size: 28rpx;
font-family: PingFang SC;
font-weight: bold;
color: #000000;
line-height: 36rpx;
.tag {
margin-left: 15rpx;
display: inline-block;
padding: 8rpx 10rpx;
font-size: 14rpx;
font-family: PingFang SC;
font-weight: 500;
color: #69b0ff;
line-height: 21rpx;
border: 1px solid #69b0ff;
border-radius: 6px;
}
}
.date {
font-size: 24rpx;
font-family: PingFang SC;
font-weight: 500;
color: #a3a3a3;
line-height: 36rpx;
}
}
}
.des {
margin-top: 40rpx;
font-size: 30rpx;
font-family: PingFang SC;
color: #000000;
line-height: 47rpx;
}
}
.swiper-box {
height: 100vh;
}
</style>

199
pages/reserve/confirm.vue Normal file
View File

@ -0,0 +1,199 @@
<template>
<view class="container">
<!-- 预约信息卡片 -->
<view class="info-card">
<view class="info-row">
<text class="info-label">预约项目</text>
<text class="info-value">{{ itemData.name }}</text>
</view>
<view class="info-row split-row">
<text class="info-label">预约时间</text>
<text class="info-value">{{ reserveTime }}</text>
</view>
<view class="info-row">
<text class="info-label">预约大厅</text>
<text class="info-value">广西区直住房保障中心</text>
</view>
<view class="info-row">
<text class="info-label">预约部门</text>
<text class="info-value">{{ itemData.deptName }}</text>
</view>
</view>
<!-- 个人信息卡片 -->
<view class="info-card">
<view class="info-row">
<text class="info-label">姓名</text>
<text class="info-value">{{ userinfo.name }}</text>
</view>
<view class="info-row split-row">
<text class="info-label">手机号</text>
<text class="info-value">{{ userinfo.phone }}</text>
</view>
<view class="info-row">
<text class="info-label">证件类型</text>
<text class="info-value">身份证</text>
</view>
<view class="info-row">
<text class="info-label">证件号码</text>
<text class="info-value">{{ userinfo.idCard }}</text>
</view>
</view>
<!-- 底部按钮栏 -->
<view class="btn-bar">
<button class="btn-back" @click="handleGoBack">返回</button>
<button class="btn-confirm" @click="handleConfirm">确认预约</button>
</view>
</view>
</template>
<script>
import {
getUserInfo,
submit
} from '@/api/reserve.js'
export default {
data() {
return {
userinfo: {},
itemData: {},
reserveTime: ''
}
},
onLoad(options) {
this.itemData = JSON.parse(decodeURIComponent(options.data))
this.reserveTime = options.time
// this.userinfo = this.$store.state.userInfo
this.getUserInfo()
},
methods: {
//
handleGoBack() {
uni.navigateBack()
},
getUserInfo(){
getUserInfo().then(res=>{
this.userinfo = res.data
})
},
//
handleConfirm() {
const params = {
userId: this.userinfo.id,
applicant: this.userinfo.name,
phone: this.userinfo.phone,
idCard: this.userinfo.idCard,
itemId: this.itemData.id,
itemName: this.itemData.name,
deptId: this.itemData.deptId,
bookingDate: this.reserveTime.slice(0, 10),
timeSlot: this.reserveTime.slice(11),
}
submit(params).then(res => {
if (res.code == 200) {
uni.showToast({
title: '预约成功!',
icon: 'success'
})
setTimeout(() => {
uni.reLaunch({
url: '/pages/home/home'
})
}, 2000)
}
}).catch(err => {})
// /
// uni.reLaunch({ url: '/pages/index/index' })
}
}
}
</script>
<style scoped>
/* 全局容器 */
.container {
min-height: 92vh;
background-color: #f5f5f5;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
display: flex;
flex-direction: column;
}
/* 信息卡片 */
.info-card {
background-color: #ffffff;
border-radius: 24rpx;
margin: 20rpx 30rpx;
padding: 0 30rpx;
}
.info-row {
display: flex;
align-items: flex-start;
padding: 20rpx 0;
}
.split-row {
border-bottom: 1rpx solid #eeeeee;
}
.info-label {
width: 220rpx;
font-size: 34rpx;
font-weight: 500;
color: #333333;
flex-shrink: 0;
}
.info-value {
flex: 1;
font-size: 34rpx;
color: #333333;
text-align: right;
line-height: 1.5;
}
/* 底部按钮栏 */
.btn-bar {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
display: flex;
padding: 20rpx 30rpx 40rpx;
background-color: #fff;
gap: 30rpx;
margin-top: auto;
box-shadow: 0 -8rpx 20rpx rgba(0, 0, 0, 0.05);
}
.btn-back {
flex: 1;
height: 88rpx;
line-height: 88rpx;
background-color: rgba(242, 247, 255, 1);
color: rgba(49, 134, 255, 1);
border-radius: 44rpx;
font-size: 32rpx;
font-weight: 500;
border: 2rpx solid rgba(208, 227, 255, 1);
}
.btn-confirm {
flex: 2;
height: 88rpx;
line-height: 88rpx;
background-color: rgba(49, 134, 255, 1);
color: #ffffff;
border-radius: 44rpx;
font-size: 32rpx;
font-weight: 500;
border: 2rpx solid rgba(208, 227, 255, 1);
}
</style>

300
pages/reserve/index.vue Normal file
View File

@ -0,0 +1,300 @@
<template>
<view class="container">
<!-- 顶部Tab切换 -->
<view class="tab-bar">
<view class="tab-item" :class="{ active: activeTab === 'department' }" @click="switchTab('department')">
单位预约 </view>
<view class="tab-item" :class="{ active: activeTab === 'person' }" @click="switchTab('person')"> 个人预约
</view>
</view>
<!-- 搜索栏 -->
<view class="search-bar">
<view class="search-input-wrap">
<view class="search-icon"></view>
<input class="search-input" v-model="searchKeyword" clearable placeholder="请输入关键字搜索"
placeholder-class="placeholder-class" @confirm="handleSearch" />
</view>
<button class="search-btn" @click="handleSearch">搜索</button>
</view>
<!-- 部门列表 -->
<!-- <view class="list-container">
<view v-if="departmentList.length > 0">
<view class="list-item" v-for="(item, index) in departmentList" :key="index"
@click="handleDeptClick(item)">
<view class="item-left">
<view class="dot"></view>
<text class="item-text">{{ item.deptName }}</text>
</view>
<view class="arrow"></view>
</view>
</view>
<view v-else class="empty-tip">暂无事项数据</view>
</view> -->
<!-- 事项列表 -->
<view class="list-container">
<view v-if="itemList.length > 0">
<view class="list-item" v-for="(item, index) in itemList" :key="index" @click="handleItemClick(item)">
<view class="item-left">
<view class="dot"></view>
<text class="item-text">{{ item.name }}</text>
</view>
<view class="arrow"></view>
</view>
</view>
</view>
</view>
</template>
<script>
import {
deptList,
itemList
} from '@/api/reserve.js'
export default {
data() {
return {
activeTab: 'department', // Tabdepartment-person-
searchKeyword: '', //
deptId: '',
//
departmentList: [],
itemList: [],
type: 2 // 1, 2
}
},
onLoad(option) {
if (option.id) {
this.deptId = option.id
// this.switchTab('person')
} else {
// this.getDeptList()
}
this.getItemList()
},
methods: {
// Tab
switchTab(tab) {
if (this.activeTab === tab) return
if (tab == 'department') {
// this.getDeptList()
this.type = 2
} else {
this.type = 1
}
this.activeTab = tab
this.searchKeyword = '' // Tab
this.getItemList()
// this.deptId = ''
},
//
handleSearch() {
// if (this.activeTab == 'department') {
// // this.getDeptList()
// } else {}
this.getItemList()
},
//
handleDeptClick(item) {
this.deptId = item.id
this.switchTab('person')
},
//
handleItemClick(item) {
const data = JSON.stringify(item)
uni.navigateTo({
url: `/pages/reserve/notice?data=${encodeURIComponent(data)}`
})
},
//
getDeptList() {
let params = {
size: 10,
current: 1,
status: 1,
deptName: this.searchKeyword
}
deptList(params).then(res => {
if (res.code === 200) {
this.departmentList = res.data.records.filter(item => item.parentId != 0)
}
})
},
//
getItemList() {
// type:1, 2
let params = {
size: 10,
current: 1,
status: 1,
// deptId: this.deptId || '',
itemType: this.type,
name: this.searchKeyword
}
itemList(params).then(res => {
if (res.code === 200) {
this.itemList = res.data.records
}
})
}
}
}
</script>
<style scoped lang="scss">
* {
box-sizing: border-box;
}
/* 全局容器 */
.container {
min-height: 100vh;
background-color: #f5f5f5;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
/* Tab栏 */
.tab-bar {
display: flex;
background-color: #ffffff;
height: 90rpx;
line-height: 90rpx;
position: relative;
}
.tab-item {
flex: 1;
text-align: center;
font-size: 32rpx;
color: #333333;
font-weight: 500;
position: relative;
}
.tab-item.active {
color: #409eff;
font-size: 36rpx;
}
.tab-item.active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 120rpx;
height: 6rpx;
background-color: #409eff;
border-radius: 3rpx;
}
/* 搜索栏 */
.search-bar {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background-color: #f5f5f5;
gap: 20rpx;
}
.search-input-wrap {
flex: 1;
display: flex;
align-items: center;
background-color: #ffffff;
border-radius: 50rpx;
height: 70rpx;
padding: 0 30rpx;
}
.search-icon {
width: 32rpx;
height: 32rpx;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999999"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>');
background-size: contain;
background-repeat: no-repeat;
margin-right: 20rpx;
}
.search-input {
flex: 1;
height: 100%;
font-size: 30rpx;
color: #333333;
background: transparent;
border: none;
outline: none;
}
.placeholder-class {
color: #999999;
font-size: 30rpx;
}
.search-btn {
width: 140rpx;
height: 70rpx;
line-height: 70rpx;
background: linear-gradient(to left, rgba(49, 134, 255, 1), rgba(21, 167, 255, 1));
color: #ffffff;
border-radius: 50rpx;
font-size: 30rpx;
font-weight: 500;
border: none;
padding: 0;
}
/* 列表容器 */
.list-container {
padding: 0 30rpx;
}
.list-item {
display: flex;
align-items: center;
justify-content: space-between;
background-color: #ffffff;
border-radius: 24rpx;
padding: 30rpx;
margin-bottom: 16rpx;
}
.item-left {
display: flex;
align-items: center;
}
.dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: linear-gradient(to bottom, rgba(49, 134, 255, 1), rgba(21, 167, 255, 1));
margin-right: 16rpx;
}
.item-text {
font-size: 34rpx;
color: #333333;
font-weight: 500;
}
.arrow {
width: 16rpx;
height: 16rpx;
border-right: 4rpx solid #165dff;
border-top: 4rpx solid #165dff;
transform: rotate(45deg);
}
/* 空状态提示 */
.empty-tip {
text-align: center;
padding: 100rpx 0;
font-size: 28rpx;
color: #999999;
}
</style>

140
pages/reserve/notice.vue Normal file
View File

@ -0,0 +1,140 @@
<template>
<view class="container">
<!-- 协议内容区域 -->
<view class="agreement-content">
<view class="article" v-html="article"> </view>
</view>
<!-- 底部按钮栏 -->
<view class="btn-bar">
<button class="btn-back" @click="handleGoBack">返回</button>
<button class="btn-confirm" @click="handleConfirm">确定预约</button>
</view>
</view>
</template>
<script>
import { noticeData } from '@/api/reserve.js'
export default {
name: 'ConfirmPage',
data() {
return {
article: '',
itemData: {}
}
},
onLoad(options) {
this.itemData = JSON.parse(decodeURIComponent(options.data))
this.getNotice()
},
methods: {
//
handleGoBack() {
uni.navigateBack()
},
//
handleConfirm() {
const data = JSON.stringify(this.itemData)
uni.navigateTo({
url: `/pages/reserve/timeSelect?data=${encodeURIComponent(data)}`
})
},
getNotice() {
let params = {
descs: 'update_time',
size: 10,
current: 1,
deptId: this.itemData.id
}
noticeData(params).then(res => {
if (res.code === 200) {
this.article = res.data.records[0].content
}
})
}
}
}
</script>
<style scoped lang="scss">
/* 🔥 uni-app 必须加这个,页面高度才会生效 */
// page {
// height: 100%;
// }
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* 最外层 flex 容器 */
.container {
display: flex;
flex-direction: column;
height: 100vh;
/* 兼容更好 */
background-color: #f5f5f5;
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
}
/* 内容区域:自动占满剩余高度 + 滚动 */
.agreement-content {
flex: 1;
overflow: auto;
padding: 40rpx 30rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
line-height: 1.6;
}
.article {
font-size: 30rpx;
color: #333;
font-weight: 500;
margin-bottom: 15rpx;
}
.list-item {
font-size: 30rpx;
color: #333;
font-weight: 500;
margin-left: 20rpx;
margin-bottom: 10rpx;
}
/* 底部按钮:固定在最下方,不使用 absolute */
.btn-bar {
display: flex;
padding: 20rpx 30rpx 40rpx;
background-color: #ffffff;
gap: 30rpx;
border-top: 1rpx solid #eee;
box-shadow: 0 -8rpx 20rpx rgba(0, 0, 0, 0.05);
}
.btn-back {
flex: 1;
height: 88rpx;
line-height: 88rpx;
background-color: rgba(242, 247, 255, 1);
color: rgba(49, 134, 255, 1);
border-radius: 44rpx;
font-size: 32rpx;
font-weight: 500;
border: 2rpx solid rgba(208, 227, 255, 1);
}
.btn-confirm {
flex: 2;
height: 88rpx;
line-height: 88rpx;
background-color: rgba(49, 134, 255, 1);
color: #ffffff;
border-radius: 44rpx;
font-size: 32rpx;
font-weight: 500;
border: 2rpx solid rgba(208, 227, 255, 1);
}
</style>

View File

@ -0,0 +1,327 @@
<template>
<view class="container">
<!-- 信息栏 -->
<view class="info-bar">
<view class="info-title">住房档案变更房屋坐落变更</view>
<view class="info-dept">公积金管理中心</view>
</view>
<!-- 日历 -->
<view class="calendar-wrap">
<view class="calendar-header">
<text class="week-item" v-for="week in weekList" :key="week">{{ week }}</text>
</view>
<view class="date-row" v-for="(row, index) in calendarList" :key="index">
<view class="date-item" :class="{
active: item && item.active,
has: item && item.isAvailable,
disabled: !item || !item.isAvailable
}" v-for="(item, idx) in row" :key="idx" @click="selectDate(item)">
<text class="date-num">{{ item ? getDay(item.date) : '无' }}</text>
<text class="date-status" v-if="item">{{ item.isAvailable ? '' : '' }}</text>
</view>
</view>
</view>
<!-- 时段 -->
<view class="time-list">
<view class="time-item" :class="{ selected: item.selected, disabled: item.remainingCount <= 0 }"
v-for="item in currentTimeSlots" :key="item.time" @click="selectTime(item)">
<view>{{ item.time }}</view>
<view>可预约 {{ item.remainingCount }}</view>
</view>
</view>
<!-- 底部 -->
<view class="footer-bar">
<view class="selected-info">已选{{ selectedDate }} {{ selectedTime }}</view>
<view class="btn-box">
<button class="btn-gray" @click="handleGoBack">返回</button>
<button class="btn-blue" @click="handleNext">下一步</button>
</view>
</view>
</view>
</template>
<script>
import {
availableDate
} from '@/api/reserve.js'
export default {
data() {
return {
weekList: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],
dateList: [],
calendarList: [],
currentTimeSlots: [],
selectedDate: "",
selectedTime: "",
itemData: {}
};
},
onLoad(options) {
this.itemData = JSON.parse(decodeURIComponent(options.data))
this.fetchData()
},
methods: {
fetchData() {
availableDate({
itemId: this.itemData.id
}).then(res => {
if (res.code === 200) {
this.dateList = res.data
this.initCalendar()
// /
const firstAva = this.dateList.find(item => item.isAvailable)
firstAva && this.selectDate(firstAva)
}
})
},
// 100% - weekDayName++
initCalendar() {
//
const row1 = Array(7).fill(null) //
const row2 = Array(7).fill(null) //
//
const weekToCol = {
"星期一": 0,
"星期二": 1,
"星期三": 2,
"星期四": 3,
"星期五": 4,
"星期六": 5,
"星期日": 6
}
//
const today = this.dateList[0]
if (!today) {
this.calendarList = [row1, row2]
return
}
const todayCol = weekToCol[today.weekDayName]
// /
this.dateList.forEach(item => {
const col = weekToCol[item.weekDayName]
if (col === undefined) return
//
if (col >= todayCol) {
row1[col] = item
} else {
row2[col] = item
}
})
//
this.calendarList = [row1]
if (row2.some(item => item)) {
this.calendarList.push(row2)
}
},
getDay(date) {
if (!date) return "";
return date.split("-")[2];
},
formatDate(date) {
const arr = date.split("-");
return arr[0] + "-" + arr[1] + "-" + arr[2];
},
selectDate(item) {
if (!item || !item.isAvailable) return;
this.selectedTime = ''
this.dateList.forEach(i => (i.active = false));
item.active = true;
this.selectedDate = this.formatDate(item.date);
this.currentTimeSlots = (item.timeSlots || []).map(t => {
return {
...t,
selected: false
};
});
if (this.currentTimeSlots.length > 0) {
this.selectTime(this.currentTimeSlots[0]);
}
},
selectTime(item) {
if (item.remainingCount <= 0) return;
this.currentTimeSlots.forEach(t => (t.selected = false));
item.selected = true;
this.selectedTime = item.time;
},
handleGoBack() {
uni.navigateBack();
},
handleNext() {
if (!this.selectedDate || !this.selectedTime) {
uni.showToast({
title: "请选择完整",
icon: "none"
});
return;
}
const data = JSON.stringify(this.itemData)
const time = this.selectedDate + " " + this.selectedTime
uni.navigateTo({
url: `/pages/reserve/confirm?data=${encodeURIComponent(data)}&time=${time}`
});
}
}
};
</script>
<style scoped>
.container {
background: #f5f7fa;
min-height: 100vh;
}
.info-bar {
background: linear-gradient(90deg, rgba(49, 134, 255, 1), #53a8ff);
padding: 30rpx;
color: #fff;
}
.info-title {
font-size: 34rpx;
margin-bottom: 10rpx;
}
.info-dept {
font-size: 28rpx;
}
.calendar-wrap {
background: #fff;
margin: 20rpx;
border-radius: 16rpx;
padding: 20rpx;
border: 2rpx solid rgba(49, 134, 255, 1);
}
.calendar-header {
display: flex;
text-align: center;
margin-bottom: 20rpx;
}
.week-item {
flex: 1;
font-size: 30rpx;
font-weight: 500;
}
.date-row {
display: flex;
margin-bottom: 20rpx;
}
.date-item {
flex: 1;
width: 100rpx;
height: 100rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.date-item.has {
color: rgba(49, 134, 255, 1);
}
.date-item.active {
background: rgba(49, 134, 255, 1);
color: #fff;
}
.date-item.disabled {
color: #ccc;
}
.date-num {
font-size: 32rpx;
}
.date-status {
font-size: 24rpx;
}
.time-list {
padding: 20rpx;
}
.time-item {
background: #fff;
border-radius: 12rpx;
padding: 30rpx;
margin-bottom: 16rpx;
display: flex;
justify-content: space-between;
font-size: 30rpx;
}
.time-item.selected {
background: #e8f3ff;
border: 2rpx solid rgba(49, 134, 255, 1);
}
.time-item.disabled {
color: #ccc;
}
.footer-bar {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background: #fff;
padding: 20rpx 30rpx 40rpx;
margin-top: 20rpx;
}
.selected-info {
text-align: center;
font-size: 28rpx;
margin-bottom: 20rpx;
}
.btn-box {
display: flex;
gap: 30rpx;
}
.btn-gray {
flex: 1;
height: 88rpx;
line-height: 88rpx;
background-color: rgba(242, 247, 255, 1);
color: rgba(49, 134, 255, 1);
border-radius: 44rpx;
font-size: 32rpx;
font-weight: 500;
border: 2rpx solid rgba(208, 227, 255, 1);
}
.btn-blue {
flex: 2;
height: 88rpx;
line-height: 88rpx;
background-color: rgba(49, 134, 255, 1);
color: #ffffff;
border-radius: 44rpx;
font-size: 32rpx;
font-weight: 500;
border: 2rpx solid rgba(208, 227, 255, 1);
}
</style>

357
pages/rich/index.vue Normal file
View File

@ -0,0 +1,357 @@
<template>
<view>
<view class="editor_toolbox" :style="{ top: `${statusBarHeight}px` }">
<u-icon name="arrow-leftward" size="40rpx" color="#303133" @click="onSave"></u-icon>
<!-- <i class="iconfont icon-save" @tap="onSave" /> -->
<i class="iconfont icon-undo" data-method="undo" @tap="edit" />
<i class="iconfont icon-redo" data-method="redo" @tap="edit" />
<i class="iconfont icon-img" data-method="insertImg" @tap="edit" />
<i class="iconfont icon-video" data-method="insertVideo" @tap="edit" />
<i class="iconfont icon-link" data-method="insertLink" @tap="edit" />
<i class="iconfont icon-text" data-method="insertText" @tap="edit" />
<i class="iconfont icon-clear" @tap="clear" />
</view>
<view :style="{ paddingTop: `${statusBarHeight + 50}px` }">
<mp-html ref="article" container-style="padding:20px" :content="content" domain="" :editable="editable" @remove="remove" />
</view>
<block v-if="modal">
<view class="mask" />
<view class="modal">
<view class="modal_title">{{ modal.title }}</view>
<input class="modal_input" :value="modal.value" maxlength="-1" auto-focus @input="modalInput" />
<view class="modal_foot">
<view class="modal_button" @tap="modalCancel">取消</view>
<view class="modal_button" style="color:#576b95;border-left:1px solid rgba(0,0,0,.1)" @tap="modalConfirm">确定</view>
</view>
</view>
</block>
</view>
</template>
<script>
import mpHtml from '@/components/rider-crud/components/mp-html/mp-html'
import http from '@/http/api.js'
//
function upload(src, type) {
return new Promise((resolve, reject) => {
console.log('上传', type === 'img' ? '图片' : '视频', '', src)
// resolve(src)
const params = {
// #ifdef MP-ALIPAY
fileType: 'image/video/audio', //
// #endif
filePath: src,
name: 'file'
}
http.upload('/api/blade-resource/oss/endpoint/put-file', params).then(res => {
resolve(res.data.link)
})
})
}
//
function remove(src) {
console.log('删除图片:', src)
// 使线
}
export default {
data() {
return {
content: null,
modal: null,
editable: true,
statusBarHeight: 0
}
},
components: {
mpHtml
},
onReady() {
/**
* @description 设置获取链接的方法
* @param {String} type 链接的类型img/video/audio/link
* @param {String} value 修改链接时这里会传入旧值
* @returns {Promise} 返回线上地址
* type 为音视频时可以返回一个数组作为源地址
* type audio 可以返回一个 object包含 srcnameauthorposter 等字段
*/
this.$refs.article.getSrc = (type, value) => {
return new Promise((resolve, reject) => {
if (type === 'img' || type === 'video') {
uni.showActionSheet({
itemList: ['本地选取', '远程链接'],
success: res => {
if (res.tapIndex === 0) {
//
if (type === 'img') {
uni.chooseImage({
count: value === undefined ? 9 : 1, // 2.2.0
success: res => {
// #ifdef MP-WEIXIN
if (res.tempFilePaths.length == 1 && wx.editImage) {
//
wx.editImage({
src: res.tempFilePaths[0],
complete: res2 => {
uni.showLoading({
title: '上传中'
})
upload(res2.tempFilePath || res.tempFilePaths[0], type).then(res => {
uni.hideLoading()
resolve(res)
})
}
})
} else {
// #endif
uni.showLoading({
title: '上传中'
})
;(async () => {
const arr = []
for (let item of res.tempFilePaths) {
//
const src = await upload(item, type)
arr.push(src)
}
return arr
})().then(res => {
uni.hideLoading()
resolve(res)
})
// #ifdef MP-WEIXIN
}
// #endif
},
fail: reject
})
} else {
uni.chooseVideo({
success: res => {
uni.showLoading({
title: '上传中'
})
upload(res.tempFilePath, type).then(res => {
uni.hideLoading()
resolve(res)
})
},
fail: reject
})
}
} else {
//
this.callback = {
resolve,
reject
}
this.$set(this, 'modal', {
title: (type === 'img' ? '图片' : '视频') + '链接',
value
})
}
}
})
} else {
this.callback = {
resolve,
reject
}
let title
if (type === 'audio') {
title = '音频链接'
} else if (type === 'link') {
title = '链接地址'
}
this.$set(this, 'modal', {
title,
value
})
}
})
}
},
onLoad(option) {
let that = this
that.content = option.data
// -
uni.getSystemInfo({
success: function(res) {
// #ifdef MP-WEIXIN
that.statusBarHeight = Number(res.statusBarHeight) + Number(res.platform == 'ios' ? 44 : 48)
// #endif
// #ifdef APP-PLUS
that.statusBarHeight = Number(res.statusBarHeight)
// #endif
}
})
},
methods: {
// //
remove(e) {
// 线
remove(e.src)
},
//
modalInput(e) {
this.value = e.detail.value
},
modalConfirm() {
this.callback.resolve(this.value || this.modal.value || '')
this.$set(this, 'modal', null)
},
modalCancel() {
this.callback.reject()
this.$set(this, 'modal', null)
},
//
edit(e) {
this.$refs.article[e.currentTarget.dataset.method]()
},
//
clear() {
uni.showModal({
title: '确认',
content: '确定清空内容吗?',
success: res => {
if (res.confirm) this.$refs.article.clear()
}
})
},
//
onSave() {
setTimeout(() => {
var content = this.$refs.article.getContent()
uni.showModal({
title: '提示',
content: '是否保存当前内容返回上一级?',
confirmText: '保存',
cancelText: '不保存',
success: res => {
if (res.confirm) {
uni.$emit('richBack', content)
}
this.editable = false
uni.navigateBack({
delta: 1
})
}
})
}, 50)
}
}
}
</script>
<style>
.editor_toolbox {
position: fixed;
top: 0;
width: 100%;
z-index: 999;
background-color: #ededed;
display: flex;
align-items: center;
padding: 5px;
box-sizing: border-box;
line-height: 1.6;
}
@font-face {
font-family: 'iconfont';
src: url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAeYAAsAAAAADlAAAAdLAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCEAgqOYItrATYCJAMkCxQABCAFhG0HcBv/CzOjdoNyEiD7nwnxpstuRCLrGmPTaffv/hnWZJHUNtWZeOD/3t03mks73vmC/3jA8SRom0aatimgfv7d9M8lBEJCIHRBKua0E7EEOqedMBEJm8N7js0MWpgq9PlMkGcTsS8NgH3X/AEbFaFGZNFvH+xR2uYofWlu9NO9ypmTgvNlW8CitMbdT7rHpgAXaKxBOfzWBdQCFgyrMV3kKtMLUFGd7GEC0JSkACq79OKAIMbDAW2W48z1QESSiQsUARGRZFyYAZ0AR8Q1ogsAjs7vhy+UBQIgwhPwR83HdB4F7R6gR25M+CrAUEwAobtM6LuBBBRA1qlLZvYO80KFg8isxmImAH1xHwzc0UPpQ/ph9cNLD+8+cn/9mnAgGQwUGkVj0B1kUhL4EP94IgQIVeCoDc62pg7uyLCRwUMCauARlsQ0ElWAq5EoAV9CogB8FzYieLq7qQM0rM5DYAriAik/Li6hNpHBj2knBBuokIJWymUizhppEknSNKNTIHnPfnR5WRIjFnmB0MgyTh9CFr9W5WgCNNfq00hdfi0KEVVL7I0kQvMYjy8OUbDAQIFwzDiGGzMKX+/PVu1sih2FbLk8PneQ0e84zT9a9GckbXu06x/h1C0hw45Twmlb0BLgUtCw7qqMfsqCGhV6bZl8tzTOhwXIwI1ZQCJLIIvccUoHE88h4XC2eGu4M/1XNB3fedRypMHlpyhbEwA5zn0qQ+4JIFxpmvQRIqHR6kOYxT8yRF6DufwjDl1JEG+8Wqk8ej0Z33StarLSdvb0fhAL+06dIRXWc4EDCLccDJ7nRTRGNUgi5S3sUk8hG4BAIuuqdJzCcXaWtWpZK0O7I0Mqs9SeBk6HJs+pMZ3cqWSo18Up8nh2vcql1drvYnRZfFaWawwlbAjnNuUeD0fz47f2GNsa/fF7o5EiX96JqiMhfWMOZ3c32aoWarUuu1GgZb3ZijxTszPBoebYYVU51WtjNSTXad3ekZzB2YUmmynf7Frv87CjOrjBApsDIHqPD//DhUKGSMSAEKRwBgqH+XDUGI0YIlaOKiYk2EnSqUEWF7XAageI3OVCf/PhsDEaNSKnhrSzKBTiQlVdDbJsPEl0ONSUS4ssTnLO4dOJ8O/y7lwLIsG9w6MxwIZTFYojgSR8c7B6ksJqZ8UOB6W0ubb0SifPuQb42mUpNFyJuQI0OLvELTPRSN5ODs3CBUKIBoDa1xrz1K/SNy3fVPVDMKK2XqdM6a9QLFycnZWRVUJ89QUvJOjZoAOfr7zKhGDrb79CEfP5M8/DTEvPngRh8AWBr9OZ66+89rX3tVtZ8tVf0r2fhur6+fMn0ALooJU7KIuS79+fpFNWX018UNVONCAU6vv7QGWHX5rbxpdvCW5JI9sFt9aUVW1pjXfr1K+5X6dOeOsts8vm/p1Iq+glCpUiObh4EVm2JbClPL5V603DrqjoTQomnlFsolUURBISwDpcroJ7b0oA02NQonc6VOv1iZVzBiYiduCcysTgHXF6OAN6GftRd0b3kZV9q0+AxEQ+IRESEvhEqIl262o2d+ZSUQfnmQcNY2dMXbp06rKxDTkJDJeW6vN3QmnfdO+etSJLmp0tSazIHjphQaZo7r6Dq2MZ3eom36nkLlp5St+X1jcrBkv4xK/79qbGfer3tXOWL0+NzJUNjuev1m2r8TwarGszs8uaVXMJRufavYvjGRWXNvLMw4a6hqnTp+OWddN3rFg+ixl4/IR5XXolag7ZzhxpDtvDmd17wTeo3HzocG+79mhzQ6GYS6SY3AUFNu1aUC5jsE6YcVIdLZk+YIqhI161tOWSVwtt2jUJey9Kv5VI+tWYJCCe8U3j1Nhp6t/ry8zF48oidd2GdLkxpPOAeiG9UXgmkpyh9YWSltIkJvUm3/2XckNk/emn1TPGhuRv5ddp5qpQ5XLRDWCzQqyzQ4tWrMxPsIRfxsInjYr7g9evq5O7w5KQAvAPYk1YW0p4kkSoGpozaS3WThNOfixfE8B+wzrpbqcdWAzzu1akq+jGw+IgeIpNPUYoW30gqPdHzI3c6qeif0AMzVuoE4HK3keDt+KE/PXzUOM3K4anQW5D40VTGYGmWAAeCflDN7ulJpW9TyBKHUAklQgJIYOROQVQQamAKkJbaMrX9fKWFqYDBmUZgJkVDBDGXhBRCAKGcdEJJzg3AKfxAiSYgAHJgFrckJIVVqPJSTBWzCD5AKVcG5mmE7ftHW1TxhL2O+o/lEBlsJjO43U3rFG6OCU87VLVgBGu4Moeh2XJ4IVzTHXqVP1xNjNZp05TrlqTVkfBWC3zDJKPPaVcm611kvj5O9qmjKVgyI/9H0qg7cNiOq/AuKG60pBT6RyedqmUacDoLVzBlTKxFNUMPnu/HFOdugaV/jijqUxVNB2eqs41gtM3LCcta4sQhsQIRxIkRQSSIRLEim8daOMprvsqcXCDRdX2cFgMy8TbcsJh6vBNMY++jmv7bXgQHHmqWy0A')
format('woff2');
}
.iconfont {
color: #303133;
flex: 1;
text-align: center;
font-family: 'iconfont' !important;
font-size: 22px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-undo:before {
content: '\e607';
}
.icon-redo:before {
content: '\e606';
}
.icon-img:before {
content: '\e6e2';
}
.icon-video:before {
content: '\e798';
}
.icon-link:before {
content: '\e60d';
}
.icon-text:before {
content: '\e6ce';
}
.icon-clear:before {
content: '\e637';
}
.icon-save:before {
content: '\e501';
}
/* 模态框 */
.modal {
position: fixed;
top: 50%;
left: 16px;
right: 16px;
background-color: #fff;
border-radius: 12px;
transform: translateY(-50%);
}
.modal_title {
padding: 32px 24px 16px;
font-size: 17px;
font-weight: 700;
text-align: center;
}
.modal_input {
display: block;
padding: 5px;
margin: 0 24px 32px 24px;
font-size: 14px;
border: 1px solid #dfe2e5;
}
.modal_foot {
display: flex;
line-height: 56px;
font-weight: 700;
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
.modal_button {
flex: 1;
text-align: center;
}
/* 蒙版 */
.mask {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: black;
opacity: 0.5;
}
</style>

188
pages/service/service.vue Normal file
View File

@ -0,0 +1,188 @@
<template>
<view class="container">
<view class="head">
<u-navbar :is-fixed="false" :border-bottom="false" :is-back="true" back-icon-name="arrow-leftward"
back-icon-color="#fff" back-icon-size="35" :background="{ background: 'linear-gradient(90deg, #69b0ff, #5f88ff)' }" title="服务"
title-color="#fff"></u-navbar>
<view class="head-bg"></view>
<!-- 我的订阅 start -->
<view class="card sub">
<view class="title">我的订阅</view>
<view class="list">
<u-grid :col="4" :border="false">
<u-grid-item bg-color="transparent" v-for="(item, index) in subscribeList" :key="index">
<navigator url="" hover-class="none" class="nav-item">
<image :src="item.img" mode="widthFix" class="nav-item-img"></image>
<view class="nav-item-name">{{ item.name }}</view>
</navigator>
</u-grid-item>
</u-grid>
</view>
</view>
<!-- 我的订阅 end -->
<!-- 服务列表 start -->
<view class="card wrap" v-for="(item, index) in serviceList" :key="index">
<view class="title">{{item.name}}</view>
<view class="list">
<u-grid :col="4" :border="false">
<u-grid-item bg-color="transparent" v-for="(_item, _index) in item.list" :key="_index">
<navigator url="" hover-class="none" class="nav-item">
<image :src="_item.img" mode="widthFix" class="nav-item-img"></image>
<view class="nav-item-name">{{ _item.name }}</view>
</navigator>
</u-grid-item>
</u-grid>
</view>
</view>
<!-- 服务列表 end -->
</view>
</view>
</template>
<script>
import {
fakeSubscribeList,
fakeServiceList,
} from "@/api/mock/service.js";
export default {
data() {
return {
//
subscribeList: [],
//
serviceList: [],
};
},
onLoad() {
//
//
fakeSubscribeList().then(data => {
this.subscribeList = data;
})
//
fakeServiceList().then(data => {
this.serviceList = data;
})
}
};
</script>
<style lang="scss">
.container {
background-color: #f7f7f7;
min-height: 100vh;
overflow: hidden;
}
.head {
position: relative;
top: 0;
left: 0;
z-index: 1;
}
.head-bg {
position: absolute;
left: 0px;
top: 0px;
z-index: -1;
width: 750rpx;
height: 270rpx;
background: linear-gradient(90deg, #69b0ff, #5f88ff);
}
.card {
margin: 30rpx;
background: #ffffff;
border-radius: 20rpx;
margin-top: 36rpx;
}
.sub {
.title {
padding-top: 36rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 30rpx;
font-family: Source Han Sans CN;
font-weight: 500;
color: #000000;
&::before {
content: '';
display: block;
width: 135rpx;
height: 1rpx;
background: #e4e7ed;
margin: 0 17rpx;
}
&::after {
content: '';
display: block;
width: 135rpx;
height: 1rpx;
background: #e4e7ed;
margin: 0 17rpx;
}
}
}
.list {
display: flex;
flex-wrap: wrap;
padding: 0 0 30rpx;
.nav-item {
width: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
font-size: 30rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #131313;
line-height: 48rpx;
opacity: 0.77;
&-img {
width: 54rpx;
height: 54rpx;
margin-bottom: 30rpx;
}
&-name {
font-size: 26rpx;
font-family: PingFang SC;
font-weight: 400;
color: #585b61;
}
}
}
.wrap {
.title {
padding: 36rpx 16rpx;
display: flex;
align-items: center;
font-size: 32rpx;
font-family: Source Han Sans CN;
font-weight: 500;
color: #000000;
&::before {
content: "";
display: block;
width: 5rpx;
height: 36rpx;
margin-right: 10rpx;
background: #3ac4d1;
}
}
}
</style>

22
pages/system/setting.vue Normal file
View File

@ -0,0 +1,22 @@
<template>
<view class="container">
<u-navbar :is-fixed="false" :border-bottom="false" :is-back="true" back-icon-name="arrow-leftward"
back-icon-size="35" :background="{ background: '#fff' }" title="设置" title-color="#000">
</u-navbar>
<view class="u-demo">
<view class="u-demo-wrap">
<view class="u-demo-title">很快到来</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
data: {}
};
},
}
</script>

252
pages/user/center.vue Normal file
View File

@ -0,0 +1,252 @@
<template>
<view class="container">
<view class="head" :style="{height: `${headHeight}rpx`}">
<u-navbar :is-fixed="false" :border-bottom="false" :is-back="false" title="个人中心" :background="{ background: 'transprent' }" title-color="#FFFFFF">
<view @click="$u.func.route('/pages/system/setting')" slot="right">
<image src="/static/images/user/setting.png" class="set-icon" mode="widthFix"></image>
</view>
</u-navbar>
<image src="/static/images/user/bg.png" class="head-bg" :style="{height: `${headHeight}rpx`}" />
<!-- 用户信息 -->
<view class="user-box">
<view class="left">
<image :src="userInfo.avatar" class="avatar" mode=""></image>
<view class="user-name">{{ userInfo.nick_name }}</view>
<view class="tag">{{ userInfo.tenant_id }}</view>
</view>
<view @click="$u.func.route('/pages/user/profile')" class="edit-btn">编辑资料</view>
</view>
<!-- 操作按钮 -->
<view class="nav">
<navigator url="" hover-class="none" class="nav-item">
<image src="/static/images/user/n1.png" class="icon" mode=""></image>
<view class="name">我的消息</view>
</navigator>
<navigator url="" hover-class="none" class="nav-item">
<image src="/static/images/user/n2.png" class="icon" mode=""></image>
<view class="name">我的待办</view>
</navigator>
<navigator url="" hover-class="none" class="nav-item">
<image src="/static/images/user/n3.png" class="icon" mode=""></image>
<view class="name">我的地址</view>
</navigator>
<navigator url="" hover-class="none" class="nav-item">
<image src="/static/images/user/n4.png" class="icon" mode=""></image>
<view class="name">我的收藏</view>
</navigator>
</view>
</view>
<!-- 功能列表 -->
<view class="cell-box">
<u-cell-group :border="false">
<u-cell-item title="我的评价">
<image slot="icon" src="/static/images/user/c1.png" class="icon" mode=""></image>
</u-cell-item>
<u-cell-item title="常见问题">
<image slot="icon" src="/static/images/user/c2.png" class="icon" mode=""></image>
</u-cell-item>
<u-cell-item title="客服中心" :border-bottom="false">
<image slot="icon" src="/static/images/user/c3.png" class="icon" mode=""></image>
</u-cell-item>
</u-cell-group>
</view>
<view class="cell-box">
<u-cell-group :border="false">
<u-cell-item title="推荐">
<image slot="icon" src="/static/images/user/c4.png" class="icon" mode=""></image>
</u-cell-item>
<u-cell-item title="评分">
<image slot="icon" src="/static/images/user/c5.png" class="icon" mode=""></image>
</u-cell-item>
<u-cell-item title="反馈">
<image slot="icon" src="/static/images/user/c6.png" class="icon" mode=""></image>
</u-cell-item>
<u-cell-item title="关于" :border-bottom="false">
<image slot="icon" src="/static/images/user/c7.png" class="icon" mode=""></image>
</u-cell-item>
</u-cell-group>
</view>
<view class="cell-box">
<u-cell-group :border="false">
<u-cell-item title="设置" :border-bottom="false" @click="$u.func.route('/pages/system/setting')">
<image slot="icon" src="/static/images/user/c8.png" class="icon" mode=""></image>
</u-cell-item>
</u-cell-group>
</view>
</view>
</template>
<script>
export default {
data() {
return {
headHeight: 582
};
},
mounted() {
this.setHeadHeight()
},
methods: {
setHeadHeight() {
const that = this
// #ifndef H5
uni.getSystemInfo({
success: function(res) {
that.headHeight = Number(res.statusBarHeight) + Number(res.platform == 'ios' ? 44 : 48) + 582
}
});
// #endif
},
}
};
</script>
<style lang="scss">
.container {
background-color: #f7f7f7;
min-height: 100vh;
overflow: hidden;
}
.head {
position: relative;
top: 0;
left: 0;
z-index: 1;
min-height: 582rpx;
overflow: hidden;
background: #55A2F9;
.set-icon {
vertical-align: middle;
width: 41rpx;
height: auto;
margin-right: 35rpx;
}
.head-bg {
position: absolute;
left: 0px;
top: 0px;
z-index: -1;
width: 750rpx;
height: 582rpx;
background: #55A2F9;
}
}
.user-box {
display: flex;
justify-content: space-between;
padding: 0 20rpx 0 64rpx;
margin-top: 36rpx;
.left {
display: flex;
flex-direction: column;
align-items: center;
.avatar {
width: 128rpx;
height: 128rpx;
background: #ffffff;
border-radius: 50%;
}
.user-name {
margin-top: 20rpx;
font-size: 36rpx;
font-family: Source Han Sans CN;
font-weight: 500;
color: #ffffff;
}
.tag {
margin-top: 20rpx;
display: flex;
justify-content: center;
align-items: center;
padding: 5rpx 16rpx;
border: 1px solid #f5f5f5;
border-radius: 7rpx;
font-size: 19rpx;
font-family: Source Han Sans CN;
font-weight: 300;
color: #ffffff;
}
}
.edit-btn {
margin-top: 20rpx;
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
width: 165rpx;
height: 54rpx;
border: 2rpx solid #f5f5f5;
border-radius: 11rpx;
font-size: 27rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #ffffff;
}
}
.nav {
display: flex;
//border-top: 2rpx solid #7dcdd6;
margin: 36rpx 38rpx 0;
padding: 36rpx 0 42rpx;
.nav-item {
width: calc(100% / 4);
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
font-size: 25rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #ffffff;
&:not(:last-of-type) {
position: relative;
&::after {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
content: '';
display: block;
width: 2rpx;
background-color: #fff;
height: 30rpx;
}
}
.icon {
width: 48rpx;
height: 48rpx;
margin-bottom: 6rpx;
}
}
}
.cell-box {
background: #ffffff;
margin: 18rpx;
.icon {
width: 32rpx;
height: 32rpx;
margin-right: 22rpx;
}
}
</style>

213
pages/user/profile.vue Normal file
View File

@ -0,0 +1,213 @@
<template>
<view class="container">
<u-navbar :is-fixed="false" :border-bottom="false" :is-back="true" back-icon-name="arrow-leftward" title="个人资料"
back-icon-size="35" :background="{ background: '#fff' }" title-color="#000">
<view class="complie" slot="right" @click="submit">完成</view>
</u-navbar>
<view class="content">
<view class="avatar">
<image :src="userInfo.avatar" class="avatar-image" mode=""></image>
<view class="a" @click="handleChooseImg">更换头像</view>
</view>
<view class="form">
<view class="cell">
<view class="name">昵称</view>
<input type="text" placeholder="请输入昵称" v-model="userInfo.nick_name" class="ipt"
placeholder-class="hold" />
</view>
<view class="cell">
<view class="name">生日</view>
<picker mode="date" class="ipt" :class="{ hold: !detail.birthday }"
@change="changeDefaultPicker('birthday', $event)">
<view>{{ detail.birthday || '请选择生日' }}</view>
</picker>
</view>
<view class="cell">
<view class="name">性别</view>
<picker mode="selector" :range="sexPicker" range-key="name" class="ipt"
:class="{ hold: !detail.sex }" @change="changePicker('sex', $event)">
<view>{{ detail.sex === 1?'男':'女' || '请选择性别' }}</view>
</picker>
</view>
<view class="cell">
<view class="name">手机号</view>
<input type="number" placeholder="请填写手机号" v-model="detail.phone" class="ipt"
placeholder-class="hold" />
</view>
<view class="cell">
<view class="name">邮箱</view>
<input v-model="detail.email" class="ipt" placeholder-class="hold" />
</view>
<!--<view class="cell">
<view class="name">国家/地区</view>
<pickRegions :defaultRegion="detail.region" v-model="detail.region"
:class="{ hold: !detail.region, ipt: true }">
<view>{{ detail.region || '请选择国家/地区' }}</view>
</pickRegions>
</view>-->
</view>
</view>
<!-- -->
<view class="back" @click="logout">退出登录</view>
</view>
</template>
<script>
import pickRegions from '@/components/pick-regions/pick-regions.vue';
export default {
components: {
pickRegions
},
data() {
return {
sexPicker: [{
name: '男'
},
{
name: '女'
}
],
detail: {}
};
},
onLoad() {
this.$u.api.userInfo().then(data => {
console.log(data);
if (data.success) {
this.detail = data.data;
}
}).catch(err => {
console.log(err)
this.$u.func.showToast({
title: err,
})
})
},
methods: {
logout() {
this.$u.func.logout();
},
submit() {
let detail = this.detail
if (!detail.phone && !/^1\d{10}$/.test(detail.phone)) {
return uni.showToast({
title: '请输入正确的手机号',
duration: 2000,
icon: 'none'
});
}
console.log(JSON.stringify(detail));
},
changeDefaultPicker(name, e) {
this.detail[name] = e.detail.value;
},
changePicker(name, e) {
this.detail[name] = this[`${name}Picker`][e.detail.value].name;
},
handleChooseImg() {
let that = this;
uni.chooseImage({
count: 1,
success(e) {
that.detail.avatar = e.tempFilePaths[0];
}
});
}
}
};
</script>
<style lang="scss">
.container {
background-color: #f7f7f7;
min-height: 100vh;
padding: 0 0 200rpx;
overflow: hidden;
}
.complie {
vertical-align: middle;
padding: 10rpx 20rpx;
font-size: 32rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #5f88ff;
}
.content {
background-color: #ffffff;
overflow: hidden;
}
.avatar {
margin-top: 30rpx;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-size: 30rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #5f88ff;
.avatar-image {
width: 162rpx;
height: 162rpx;
background: #ffffff;
border-radius: 50%;
background-color: #82848a;
margin-bottom: 15rpx;
}
}
.form {
padding: 0 36rpx;
.cell {
display: flex;
justify-content: space-between;
align-items: center;
padding: 40rpx 0;
font-size: 30rpx;
font-family: Source Han Sans CN;
font-weight: bold;
color: #666666;
&:not(:last-of-type) {
border-bottom: 2rpx solid #efefef;
}
.ipt {
flex: 1;
margin-left: 40rpx;
text-align: right;
font-size: 23rpx;
font-family: Source Han Sans CN;
font-weight: bold;
color: #333333;
}
.hold {
font-size: 27rpx;
font-family: Source Han Sans CN;
font-weight: bold;
color: #c8c8ce;
}
}
}
.back {
margin-top: 40rpx;
text-align: center;
height: 109rpx;
background: #ffffff;
font-size: 34rpx;
font-family: Source Han Sans CN;
font-weight: bold;
color: #141414;
line-height: 109rpx;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
static/images/head_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

BIN
static/images/home/date.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
static/images/home/n1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
static/images/home/n2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

BIN
static/images/home/n3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
static/images/home/n4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
static/images/home/n5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
static/images/home/qr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
static/images/home/s1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
static/images/home/s2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

BIN
static/images/home/s3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
static/images/home/s4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
static/images/home/s5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
static/images/home/s6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
static/images/home/s7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
static/images/home/s8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
static/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

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