vue3+arco网页版webos系统|vite5+pinia2仿mac/windows桌面os
vue3-webos一款基于最新前端技术vite5.x+vue3+pinia2+arco-design+sortablejs搭建的网页版os管理系统。内置macos和windows两种桌面、支持自定义桌面栅格布局引擎、可拖拽式桌面菜单/程序坞菜单等功能。
原创自研vite5+vue3+arco-design仿macOS网页版os桌面管理系统
vue3-webos使用vite.js搭建项目模板,采用arco.design组件库。
运用技术
- 编辑器:Vscode
- 技术框架:vite5.3.3+vue3.4.31+vue-router4.4+pinia2
- UI组件库:arco-design^2.55.3 (字节桌面版vue3组件库)
- 状态管理:pinia^2.1.7
- 图表插件:echarts^5.5.1
- 拖拽组件:sortablejs^1.15.2
- 富文本编辑器:wangeditor^4.7.15
- 模拟数据:mockjs^1.1.0
- 样式编译:sass^1.77.8
- 构建工具:vite^5.3.3
前段时间有分享一款vite5+vue3原创网页版聊天实例。感兴趣的可以去看看。
https://ask.dcloud.net.cn/article/41153
项目结构目录
目前整个vue3-macos项目已经托管到我的原创作品集,有需要的可以去瞅瞅。
https://gf.bilibili.com/item/detail/1106413011
main.js配置
import { createApp } from 'vue'
import './style.scss'
import App from './App.vue'
// 引入arco.design组件库
import ArcoDesign from '@arco-design/web-vue'
import '@arco-design/web-vue/dist/arco.css'
// 额外引入图标库
import ArcoIcon from '@arco-design/web-vue/es/icon'
import VEPlus from 've-plus'
import 've-plus/dist/ve-plus.css'
// 引入路由及状态管理
import Router from './router'
import Pinia from './pinia'
const app = createApp(App)
app
.use(ArcoDesign)
.use(ArcoIcon)
.use(VEPlus)
.use(Router)
.use(Pinia)
.mount('#app')
vue3批量路由配置
使用vite.js提供的import.meta.glob批量导入路由配置。
/**
* 路由管理Router
* @author andy
*/
import { createRouter, createWebHashHistory } from 'vue-router'
import { authState } from '@/pinia/modules/auth'
import Layout from '@/layouts/index.vue'
// 批量导入路由
const modules = import.meta.glob('./modules/*.js', { eager: true })
console.log(modules)
const patchRouters = Object.keys(modules).map(key => modules[key].default).flat()
console.log(patchRouters)
/**
* meta配置
* @param meta.requireAuth 需登录验证页面
*/
const routes = [
{
path: '/',
redirect: '/desktop',
},
...patchRouters,
// 错误模块
{
path: '/:pathMatch(.*)*',
redirect: '/404',
component: Layout,
meta: {
title: '404error',
},
children: [
{
path: '404',
component: () => import('@/views/error/404.vue'),
}
]
},
]
const router = createRouter({
history: createWebHashHistory(),
routes,
})
// 全局路由钩子拦截
router.beforeEach((to, from) => {
const authstate = authState()
// 登录验证
if(to?.meta?.requireAuth && !authstate.authorization) {
console.log('你还未登录!')
return {
path: '/login'
}
}
})
router.afterEach(() => {
// ...
})
router.onError(error => {
console.warn('[Router Error]', error)
})
桌面公共布局模板
vite-osx内置了macos和windows两种桌面。
<script setup>
import { appState } from '@/pinia/modules/app'
// 引入布局模板
import MacosLayout from './template/macos.vue'
import WindowsLayout from './template/windows.vue'
const appstate = appState()
const DeskLayout = {
macos: MacosLayout,
windows: WindowsLayout
}
</script>
<template>
<div
class="vu__container desktop flexbox flex-alignc flex-justifyc"
:style="{'--themeSkin': appstate.config.skin}"
@contextmenu.prevent
>
<component :is="DeskLayout[appstate.config.layout]" />
</div>
</template>
<template>
<div class="vu__layout flexbox flex-col">
<div class="vu__layout-header">
<Toolbar />
</div>
<div class="vu__layout-body flex1 flexbox">
<Desk />
</div>
<div class="vu__layout-footer">
<Dock />
</div>
<!-- 悬浮球(辅助触控) -->
<Touch />
</div>
</template>
vue3栅格布局桌面
// 自定义变量(桌面图标)
const deskVariable = ref({
'--icon-radius': '8px', // 圆角
'--icon-size': '60px', // 图标尺寸(设置rpx自定义手机设备)
'--icon-gap-col': '30px', // 水平间距
'--icon-gap-row': '30px', // 垂直间距
'--icon-labelSize': '12px', // 标签文字大小
'--icon-labelColor': '#fff', // 标签颜色
'--icon-fit': 'contain', // 图标自适应模式
})
/*
* 桌面菜单json配置项 by Andy Q:282310962
*/
const deskMenu = [
{
pid: 20240507001,
list: [
{imgico: markRaw(Today), size: '2x2'},
{label: '便签', imgico: markRaw(NoteBook), size: '4x2'},
...
]
},
{
pid: 20240509002,
list: [
{label: 'Appstore', imgico: '/static/mac/appstore.png'},
{label: '地图', imgico: '/static/mac/maps.png'},
...
]
},
{
pid: 20240510001,
list: [
{label: 'Github', imgico: '/static/svg/github.svg', link: 'https://github.com/', background: '#607d8b',},
...
]
},
{
uid: 'd141f210-207e-1e8e-9950-9deefac27e48',
list: [
{label: 'Vite^5.3.3', imgico: 'https://vitejs.dev/logo.svg', link: 'https://vitejs.dev/'},
...
{
label: '组件',
children: [
{label: '表格', imgico: '/static/svg/table.svg', path: '/components/table/all'},
{label: '自定义表格', imgico: '/static/svg/table.svg', path: '/components/table/custom'},
...
]
},
{label: 'ChatGPT', imgico: '/static/svg/chatgpt.svg', link: 'https://openai.com/chatgpt/', background: '#15A17F',},
{label: 'Bilibili', imgico: '/static/svg/bilibili.svg', link: 'https://www.bilibili.com/', background: '#ff6899',},
{
label: '个人中心',
children: [
{label: '主页', imgico: '/static/svg/my.svg', path: '/setting'},
...
]
},
{
label: '设置',
children: [
{label: '网站设置', imgico: '/static/svg/settings.svg', path: '/setting/system/website'},
{label: '邮件服务', imgico: '/static/mac/mail.png', path: '/setting/system/mail'},
]
},
{
label: '公众号', imgico: markRaw(IconWechat), color: '#07c160',
onClick: () => {
...
}
},
]
}
]
以上就是vue3+pinia2+arco.design实战仿macos和windows桌面os系统的一些分享知识。
作者:xiaoyan2017
链接: https://segmentfault.com/a/1190000045102738
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
vue3-webos一款基于最新前端技术vite5.x+vue3+pinia2+arco-design+sortablejs搭建的网页版os管理系统。内置macos和windows两种桌面、支持自定义桌面栅格布局引擎、可拖拽式桌面菜单/程序坞菜单等功能。
原创自研vite5+vue3+arco-design仿macOS网页版os桌面管理系统
vue3-webos使用vite.js搭建项目模板,采用arco.design组件库。
运用技术
- 编辑器:Vscode
- 技术框架:vite5.3.3+vue3.4.31+vue-router4.4+pinia2
- UI组件库:arco-design^2.55.3 (字节桌面版vue3组件库)
- 状态管理:pinia^2.1.7
- 图表插件:echarts^5.5.1
- 拖拽组件:sortablejs^1.15.2
- 富文本编辑器:wangeditor^4.7.15
- 模拟数据:mockjs^1.1.0
- 样式编译:sass^1.77.8
- 构建工具:vite^5.3.3
前段时间有分享一款vite5+vue3原创网页版聊天实例。感兴趣的可以去看看。
https://ask.dcloud.net.cn/article/41153
项目结构目录
目前整个vue3-macos项目已经托管到我的原创作品集,有需要的可以去瞅瞅。
https://gf.bilibili.com/item/detail/1106413011
main.js配置
import { createApp } from 'vue'
import './style.scss'
import App from './App.vue'
// 引入arco.design组件库
import ArcoDesign from '@arco-design/web-vue'
import '@arco-design/web-vue/dist/arco.css'
// 额外引入图标库
import ArcoIcon from '@arco-design/web-vue/es/icon'
import VEPlus from 've-plus'
import 've-plus/dist/ve-plus.css'
// 引入路由及状态管理
import Router from './router'
import Pinia from './pinia'
const app = createApp(App)
app
.use(ArcoDesign)
.use(ArcoIcon)
.use(VEPlus)
.use(Router)
.use(Pinia)
.mount('#app')
vue3批量路由配置
使用vite.js提供的import.meta.glob批量导入路由配置。
/**
* 路由管理Router
* @author andy
*/
import { createRouter, createWebHashHistory } from 'vue-router'
import { authState } from '@/pinia/modules/auth'
import Layout from '@/layouts/index.vue'
// 批量导入路由
const modules = import.meta.glob('./modules/*.js', { eager: true })
console.log(modules)
const patchRouters = Object.keys(modules).map(key => modules[key].default).flat()
console.log(patchRouters)
/**
* meta配置
* @param meta.requireAuth 需登录验证页面
*/
const routes = [
{
path: '/',
redirect: '/desktop',
},
...patchRouters,
// 错误模块
{
path: '/:pathMatch(.*)*',
redirect: '/404',
component: Layout,
meta: {
title: '404error',
},
children: [
{
path: '404',
component: () => import('@/views/error/404.vue'),
}
]
},
]
const router = createRouter({
history: createWebHashHistory(),
routes,
})
// 全局路由钩子拦截
router.beforeEach((to, from) => {
const authstate = authState()
// 登录验证
if(to?.meta?.requireAuth && !authstate.authorization) {
console.log('你还未登录!')
return {
path: '/login'
}
}
})
router.afterEach(() => {
// ...
})
router.onError(error => {
console.warn('[Router Error]', error)
})
桌面公共布局模板
vite-osx内置了macos和windows两种桌面。
<script setup>
import { appState } from '@/pinia/modules/app'
// 引入布局模板
import MacosLayout from './template/macos.vue'
import WindowsLayout from './template/windows.vue'
const appstate = appState()
const DeskLayout = {
macos: MacosLayout,
windows: WindowsLayout
}
</script>
<template>
<div
class="vu__container desktop flexbox flex-alignc flex-justifyc"
:style="{'--themeSkin': appstate.config.skin}"
@contextmenu.prevent
>
<component :is="DeskLayout[appstate.config.layout]" />
</div>
</template>
<template>
<div class="vu__layout flexbox flex-col">
<div class="vu__layout-header">
<Toolbar />
</div>
<div class="vu__layout-body flex1 flexbox">
<Desk />
</div>
<div class="vu__layout-footer">
<Dock />
</div>
<!-- 悬浮球(辅助触控) -->
<Touch />
</div>
</template>
vue3栅格布局桌面
// 自定义变量(桌面图标)
const deskVariable = ref({
'--icon-radius': '8px', // 圆角
'--icon-size': '60px', // 图标尺寸(设置rpx自定义手机设备)
'--icon-gap-col': '30px', // 水平间距
'--icon-gap-row': '30px', // 垂直间距
'--icon-labelSize': '12px', // 标签文字大小
'--icon-labelColor': '#fff', // 标签颜色
'--icon-fit': 'contain', // 图标自适应模式
})
/*
* 桌面菜单json配置项 by Andy Q:282310962
*/
const deskMenu = [
{
pid: 20240507001,
list: [
{imgico: markRaw(Today), size: '2x2'},
{label: '便签', imgico: markRaw(NoteBook), size: '4x2'},
...
]
},
{
pid: 20240509002,
list: [
{label: 'Appstore', imgico: '/static/mac/appstore.png'},
{label: '地图', imgico: '/static/mac/maps.png'},
...
]
},
{
pid: 20240510001,
list: [
{label: 'Github', imgico: '/static/svg/github.svg', link: 'https://github.com/', background: '#607d8b',},
...
]
},
{
uid: 'd141f210-207e-1e8e-9950-9deefac27e48',
list: [
{label: 'Vite^5.3.3', imgico: 'https://vitejs.dev/logo.svg', link: 'https://vitejs.dev/'},
...
{
label: '组件',
children: [
{label: '表格', imgico: '/static/svg/table.svg', path: '/components/table/all'},
{label: '自定义表格', imgico: '/static/svg/table.svg', path: '/components/table/custom'},
...
]
},
{label: 'ChatGPT', imgico: '/static/svg/chatgpt.svg', link: 'https://openai.com/chatgpt/', background: '#15A17F',},
{label: 'Bilibili', imgico: '/static/svg/bilibili.svg', link: 'https://www.bilibili.com/', background: '#ff6899',},
{
label: '个人中心',
children: [
{label: '主页', imgico: '/static/svg/my.svg', path: '/setting'},
...
]
},
{
label: '设置',
children: [
{label: '网站设置', imgico: '/static/svg/settings.svg', path: '/setting/system/website'},
{label: '邮件服务', imgico: '/static/mac/mail.png', path: '/setting/system/mail'},
]
},
{
label: '公众号', imgico: markRaw(IconWechat), color: '#07c160',
onClick: () => {
...
}
},
]
}
]
以上就是vue3+pinia2+arco.design实战仿macos和windows桌面os系统的一些分享知识。
作者:xiaoyan2017
链接: https://segmentfault.com/a/1190000045102738
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
uni.chooseLocation打开查找不到周边位置信息
调用uni.chooseLocation方法本地运行和真机测试没问题;但是打包后就查找不到周边位置;(用的高德地图;可以确定地图配置没问题;位置信息权限也打开了)
调用uni.chooseLocation方法本地运行和真机测试没问题;但是打包后就查找不到周边位置;(用的高德地图;可以确定地图配置没问题;位置信息权限也打开了)
unipush1.0 ios离线收不到消息的问题
最近有项目使用了uni-push1.0来进行消息推送功能,这里主要分享一下IOS的离线推送收不到消息的问题
IOS离线收不到消息,基本是上传的推送证书有问题
贴一下我的相关文件:
两个注意点:
1.profile/CertificateSigningRequest.certSigningRequest :证书请求文件
在 iOS Dev Center创建certificates时使用同一个证书请求文件,包括创建推送证书
2.profile/push.p12:推送证书
需要上传的推送证书,不要用这个证书去打包!!或者上传你的打包证书
如何测试上传的推送证书是否生效
在【Uni Push】-【配置管理】-【故障排查】-【 检测CID的状态及信息:】中输入CID查询,拿到苹果的devicetoken
把这个devicetoken复制一下
在【Uni Push】-【配置管理】-【故障排查】- 【应用配置】点击右下方的 立即测试,粘贴刚才的devicetoken,去掉后面的【,,,】
如果devicetoken后面带着 developement,说明是开发环境,在测试的时候需要选择开发环境,也是同样需要去掉后面【,,developement,】
测试如果弹出 连接异常 就是证书不对,建议再走一遍证书生成的流程,重新上传证书需要重新提交云打包
证书正常,应用离线也可以收到消息
设置角标
离线推送一般会设置应用角标,由于使用unipush1.0,后端调用个推推送接口的参数:auto_badge 设置为 1就行
"push_channel": {
“android”:{},
},
"ios": {
"aps": {
"alert": {
"body": "告警测试",
"title": "测试"
},
"content_available": 0,
"sound": "default"
},
"auto_badge": " 1",
"payload": "{\"title\":\"测试\",\"content\":\"告警测试\"}"
}
}
应用监听通知栏的点击事件,然后再将角标清0,建议写在App.vue里面
plus.push.addEventListener('click', (msg) => {
console.log('click', msg);
plus.runtime.setBadgeNumber(0)
// TODO
})
有问题可以联系 Q:2377995017
最近有项目使用了uni-push1.0来进行消息推送功能,这里主要分享一下IOS的离线推送收不到消息的问题
IOS离线收不到消息,基本是上传的推送证书有问题
贴一下我的相关文件:
两个注意点:
1.profile/CertificateSigningRequest.certSigningRequest :证书请求文件
在 iOS Dev Center创建certificates时使用同一个证书请求文件,包括创建推送证书
2.profile/push.p12:推送证书
需要上传的推送证书,不要用这个证书去打包!!或者上传你的打包证书
如何测试上传的推送证书是否生效
在【Uni Push】-【配置管理】-【故障排查】-【 检测CID的状态及信息:】中输入CID查询,拿到苹果的devicetoken
把这个devicetoken复制一下
在【Uni Push】-【配置管理】-【故障排查】- 【应用配置】点击右下方的 立即测试,粘贴刚才的devicetoken,去掉后面的【,,,】
如果devicetoken后面带着 developement,说明是开发环境,在测试的时候需要选择开发环境,也是同样需要去掉后面【,,developement,】
测试如果弹出 连接异常 就是证书不对,建议再走一遍证书生成的流程,重新上传证书需要重新提交云打包
证书正常,应用离线也可以收到消息
设置角标
离线推送一般会设置应用角标,由于使用unipush1.0,后端调用个推推送接口的参数:auto_badge 设置为 1就行
"push_channel": {
“android”:{},
},
"ios": {
"aps": {
"alert": {
"body": "告警测试",
"title": "测试"
},
"content_available": 0,
"sound": "default"
},
"auto_badge": " 1",
"payload": "{\"title\":\"测试\",\"content\":\"告警测试\"}"
}
}
应用监听通知栏的点击事件,然后再将角标清0,建议写在App.vue里面
plus.push.addEventListener('click', (msg) => {
console.log('click', msg);
plus.runtime.setBadgeNumber(0)
// TODO
})
有问题可以联系 Q:2377995017
收起阅读 »5+app重写mui.back
mui.plusReady(function() {
// 保存原始的mui.back函数
var originalBack = mui.back;
// 重写mui.back函数
mui.back = function() {
// 在此处添加自定义逻辑
console.log('自定义的返回操作');
// 调用原始的mui.back函数
// originalBack();
};
}) mui.plusReady(function() {
// 保存原始的mui.back函数
var originalBack = mui.back;
// 重写mui.back函数
mui.back = function() {
// 在此处添加自定义逻辑
console.log('自定义的返回操作');
// 调用原始的mui.back函数
// originalBack();
};
}) 收起阅读 »
在manifest,json中填入的密钥,打包成apk后被解包,在AndroidManifest.xml中可以明文看到
如题,请问这个问题作为开发者要怎么规避?
在manifest,json中填入的密钥,打包成apk后被解包,在AndroidManifest.xml中可以明文看到
如题,请问这个问题作为开发者要怎么规避?
在manifest,json中填入的密钥,打包成apk后被解包,在AndroidManifest.xml中可以明文看到
经纬度圆形围栏校验
https://bcapi01.luxsan-ict.com:8443/imesapi/api/ImesWebApi_05_ShipData_01/Save501ShipData?askPort=1003
[ {
"recordid": "a1581ed0-17b1-4d58-a118-bb6f9d41ffb2",
"componentserialnumber": "string",
"suppliername": "string",
"applepartnumber": "string",
"lotcode": "string",
"shipdate": "string",
"palletnumber": "string",
"boxnumber": "string",
"sscC18": "string",
"shiptosuppliername": "string",
"shiptosuppliersite": "string",
"product": "string",
"sndatecode": "string",
"sncreatetime": "string",
"snweek": "string",
"pono": "string",
"shiptoipn": "string",
"workorder": "string",
"operator": "string",
"version": "string"
"componenttype": "string"
} ]
shortcuts: [{
text: '最近90天',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 1000 24 * 90); // 设置开始时间为90天前的时间点
picker.$emit('pick', [start, end]);
}
}]
disabledDate(time) {
// 如果 value 中没有开始日期,则不限制结束日期
if (!this.value[0]) return false;
// 计算90天后的日期
const maxDate = new Date(this.value[0]);
maxDate.setDate(maxDate.getDate() + 90);
// 禁用超过90天的日期
return time.getTime() > maxDate.getTime();
}
linear-gradient(to left, #196aa8, #196aa8) left top no-repeat,
linear-gradient(to bottom, #196aa8, #196aa8) left top no-repeat,
linear-gradient(to left, #196aa8, #196aa8) right top no-repeat,
linear-gradient(to bottom, #196aa8, #196aa8) right top no-repeat,
linear-gradient(to left, #196aa8, #196aa8) left bottom no-repeat,
linear-gradient(to bottom, #196aa8, #196aa8) left bottom no-repeat,
linear-gradient(to left, #196aa8, #196AA8) right bottom no-repeat,
linear-gradient(to left, #196aa8, #196aa8) right bottom no-repeat;
background-size: 1px 20px, 20px 1px, 1px 20px, 20px 1px;
graphic: {
type: 'text',
left: 'center',
top: 'center',
style: {
text: '中心文字',
fontSize: 20, // 文字大小
fill: '#333' // 文字颜色
}
}
getColumnWidth(prop) {
let maxWidth = 0;
this.tableData.forEach(row => {
const cellContent = row[prop] ? row[prop].toString() : '';
const span = document.createElement('span');
span.style.visibility = 'hidden';
span.style.position = 'absolute';
span.style.whiteSpace = 'nowrap';
span.style.fontSize = '14px'; // 根据你的字体大小设置
span.innerText = cellContent;
document.body.appendChild(span);
const width = span.offsetWidth;
document.body.removeChild(span);
maxWidth = Math.max(maxWidth, width);
});
return ${maxWidth + 20}px; // 额外增加一些宽度以应对padding等
},
@keyframes moveBackground {
0% {
background-position: 0 0;
}
100% {
background-position: 100% 0; / 或者根据需要调整为 0 100% 实现垂直移动 /
}
}
.background-animation {
background-image: url('your-image.jpg');
background-repeat: repeat-x; / 或者 repeat-y,取决于你的需求 /
animation: moveBackground 5s linear infinite;
}
option = {
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left: 'center'
},
series: [
{
name: 'Access From',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 1048, name: 'Search Engine' },
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' }
]
}
]
};
<el-popover
placement="right"
width="400"
trigger="click">
<el-table :data="gridData">
<el-table-column width="150" property="date" label="日期"></el-table-column>
<el-table-column width="100" property="name" label="姓名"></el-table-column>
<el-table-column width="300" property="address" label="地址"></el-table-column>
</el-table>
<el-button slot="reference">click 激活</el-button>
</el-popover>
// 计算点到圆心的距离的函数
function distance(lat1, lng1, lat2, lng2) {
const radius = 6378137.0; // 地球半径
const rad = Math.PI / 180.0;
const lat1rad = lat1 rad;
const lat2rad = lat2 rad;
const a = Math.sin(lat1rad) Math.sin(lat2rad) + Math.cos(lat1rad) Math.cos(lat2rad) Math.cos((lng2 - lng1) rad);
return radius * Math.acos(a);
}
// 检查点是否在圆内的函数
function isPointInCircle(lat, lng, circleLat, circleLng, radius) {
const d = distance(lat, lng, circleLat, circleLng);
return d <= radius;
}
// 使用示例
const pointLat = 39.9903; // 点的纬度
const pointLng = 116.4814; // 点的经度
const circleLat = 39.9109; // 圆心的纬度
const circleLng = 116.4119; // 圆心的经度
const radius = 1000; // 圆的半径,单位为米
const isInside = isPointInCircle(pointLat, pointLng, circleLat, circleLng, radius);
console.log(isInside ? '点在圆内' : '点在圆外');
<dv-scroll-board :config="config" style="width:500px;height:220px" /> headerBGC oddRowBGC evenRowBGC waitTime rowNum
时间禁用
timeRange: '', // 绑定值
pickerOptions: {
// 时间跨度限制为2天
onPick: time => {
if (time.minDate && !time.maxDate) {
// 当选择开始日期时,设置结束日期的最小值为开始日期
this.timeRange = time.minDate;
} else if (time.maxDate) {
// 当选择结束日期时,清除开始日期的值
this.timeRange = '';
}
},
disabledDate: time => {
const today = new Date(); // 当前日期
if (this.timeRange) {
// 如果设置了开始日期,则禁止选择今天的日期之前的日期作为结束日期
return time.getTime() < today.getTime();
} else {
// 如果没有设置开始日期,则允许选择任何日期(这里可以根据需求自定义)
return false;
}
}
}
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>
<script>
export default {
data() {
return {
dialogImageUrl: '',
dialogVisible: false
};
},
methods: {
handleRemove(file, fileList) {
console.log(file, fileList);
},
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url;
this.dialogVisible = true;
}
}
}
</script>
https://bcapi01.luxsan-ict.com:8443/imesapi/api/ImesWebApi_05_ShipData_01/Save501ShipData?askPort=1003
[ {
"recordid": "a1581ed0-17b1-4d58-a118-bb6f9d41ffb2",
"componentserialnumber": "string",
"suppliername": "string",
"applepartnumber": "string",
"lotcode": "string",
"shipdate": "string",
"palletnumber": "string",
"boxnumber": "string",
"sscC18": "string",
"shiptosuppliername": "string",
"shiptosuppliersite": "string",
"product": "string",
"sndatecode": "string",
"sncreatetime": "string",
"snweek": "string",
"pono": "string",
"shiptoipn": "string",
"workorder": "string",
"operator": "string",
"version": "string"
"componenttype": "string"
} ]
shortcuts: [{
text: '最近90天',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 1000 24 * 90); // 设置开始时间为90天前的时间点
picker.$emit('pick', [start, end]);
}
}]
disabledDate(time) {
// 如果 value 中没有开始日期,则不限制结束日期
if (!this.value[0]) return false;
// 计算90天后的日期
const maxDate = new Date(this.value[0]);
maxDate.setDate(maxDate.getDate() + 90);
// 禁用超过90天的日期
return time.getTime() > maxDate.getTime();
}
linear-gradient(to left, #196aa8, #196aa8) left top no-repeat,
linear-gradient(to bottom, #196aa8, #196aa8) left top no-repeat,
linear-gradient(to left, #196aa8, #196aa8) right top no-repeat,
linear-gradient(to bottom, #196aa8, #196aa8) right top no-repeat,
linear-gradient(to left, #196aa8, #196aa8) left bottom no-repeat,
linear-gradient(to bottom, #196aa8, #196aa8) left bottom no-repeat,
linear-gradient(to left, #196aa8, #196AA8) right bottom no-repeat,
linear-gradient(to left, #196aa8, #196aa8) right bottom no-repeat;
background-size: 1px 20px, 20px 1px, 1px 20px, 20px 1px;
graphic: {
type: 'text',
left: 'center',
top: 'center',
style: {
text: '中心文字',
fontSize: 20, // 文字大小
fill: '#333' // 文字颜色
}
}
getColumnWidth(prop) {
let maxWidth = 0;
this.tableData.forEach(row => {
const cellContent = row[prop] ? row[prop].toString() : '';
const span = document.createElement('span');
span.style.visibility = 'hidden';
span.style.position = 'absolute';
span.style.whiteSpace = 'nowrap';
span.style.fontSize = '14px'; // 根据你的字体大小设置
span.innerText = cellContent;
document.body.appendChild(span);
const width = span.offsetWidth;
document.body.removeChild(span);
maxWidth = Math.max(maxWidth, width);
});
return ${maxWidth + 20}px; // 额外增加一些宽度以应对padding等
},
@keyframes moveBackground {
0% {
background-position: 0 0;
}
100% {
background-position: 100% 0; / 或者根据需要调整为 0 100% 实现垂直移动 /
}
}
.background-animation {
background-image: url('your-image.jpg');
background-repeat: repeat-x; / 或者 repeat-y,取决于你的需求 /
animation: moveBackground 5s linear infinite;
}
option = {
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left: 'center'
},
series: [
{
name: 'Access From',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 1048, name: 'Search Engine' },
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' }
]
}
]
};
<el-popover
placement="right"
width="400"
trigger="click">
<el-table :data="gridData">
<el-table-column width="150" property="date" label="日期"></el-table-column>
<el-table-column width="100" property="name" label="姓名"></el-table-column>
<el-table-column width="300" property="address" label="地址"></el-table-column>
</el-table>
<el-button slot="reference">click 激活</el-button>
</el-popover>
// 计算点到圆心的距离的函数
function distance(lat1, lng1, lat2, lng2) {
const radius = 6378137.0; // 地球半径
const rad = Math.PI / 180.0;
const lat1rad = lat1 rad;
const lat2rad = lat2 rad;
const a = Math.sin(lat1rad) Math.sin(lat2rad) + Math.cos(lat1rad) Math.cos(lat2rad) Math.cos((lng2 - lng1) rad);
return radius * Math.acos(a);
}
// 检查点是否在圆内的函数
function isPointInCircle(lat, lng, circleLat, circleLng, radius) {
const d = distance(lat, lng, circleLat, circleLng);
return d <= radius;
}
// 使用示例
const pointLat = 39.9903; // 点的纬度
const pointLng = 116.4814; // 点的经度
const circleLat = 39.9109; // 圆心的纬度
const circleLng = 116.4119; // 圆心的经度
const radius = 1000; // 圆的半径,单位为米
const isInside = isPointInCircle(pointLat, pointLng, circleLat, circleLng, radius);
console.log(isInside ? '点在圆内' : '点在圆外');
<dv-scroll-board :config="config" style="width:500px;height:220px" /> headerBGC oddRowBGC evenRowBGC waitTime rowNum
时间禁用
timeRange: '', // 绑定值
pickerOptions: {
// 时间跨度限制为2天
onPick: time => {
if (time.minDate && !time.maxDate) {
// 当选择开始日期时,设置结束日期的最小值为开始日期
this.timeRange = time.minDate;
} else if (time.maxDate) {
// 当选择结束日期时,清除开始日期的值
this.timeRange = '';
}
},
disabledDate: time => {
const today = new Date(); // 当前日期
if (this.timeRange) {
// 如果设置了开始日期,则禁止选择今天的日期之前的日期作为结束日期
return time.getTime() < today.getTime();
} else {
// 如果没有设置开始日期,则允许选择任何日期(这里可以根据需求自定义)
return false;
}
}
}
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>
<script>
export default {
data() {
return {
dialogImageUrl: '',
dialogVisible: false
};
},
methods: {
handleRemove(file, fileList) {
console.log(file, fileList);
},
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url;
this.dialogVisible = true;
}
}
}
</script> 收起阅读 »
【安卓权限说明顶部蒙层】uniapp项目全局监听权限,HBuilderX (4.0+) android 平台支持;上架华为应用市场审核所需
文章不再更新,请前往插件市场下载使用,插件地址:https://ext.dcloud.net.cn/plugin?id=22675
安卓离线打包(本地打包)需要添加一些sdk,具体看这篇文章https://ask.dcloud.net.cn/article/41380
更新时间:2024-08-12 14:29
旧版时间:2024-08-12 14:06
测试部分,可能有bug
目前发现bug未解决
1、保存代码,代码热更新后,触发监听会报Error: [JS Framework] Failed to receiveTasks, instance (1) is not available.这个错误,重新编译正常
模拟器效果截图
permissionListener.js
/**
* @author cai
* @time 2024-08-12 14:29
* uni.createRequestPermissionListener()
* 文档地址:https://uniapp.dcloud.net.cn/api/system/create-request-permission-listener.html
* 注意:HBuilderX (4.0+) android 平台支持;HBuilderX 4.01 Vue2项目需要使用自定义基座测试监听权限申请的功能,标准基座暂不支持测试。
*/
// #ifndef APP
export default null;
// #endif
// #ifdef APP
const { osName } = uni.getSystemInfoSync();
let permissionListener = null;
// 是安卓平台,同时有uni.createRequestPermissionListener这个api
if (osName === "android" && uni.createRequestPermissionListener) {
permissionListener = uni.createRequestPermissionListener();
}
let canRunListener = true; // 是否可以执行所有监听方法
let canStopListener = true; // 是否可以执行取消所有监听方法,避免唤起权限会触发App.vue的onHide生命周期
const permissionEnums = {
"ACCESS_COARSE_LOCATION": {
name: "定位",
explain: "展示附近店铺、填写收货地址等相关功能"
},
"ACCESS_FINE_LOCATION": {
name: "定位",
explain: "展示附近店铺、填写收货地址等相关功能"
},
"READ_EXTERNAL_STORAGE": {
name: "存储",
explain: "上传图片、上传视频等相关功能"
},
"CAMERA": {
name: "相机",
explain: "扫二维码、拍摄图片等相关功能"
},
"WRITE_EXTERNAL_STORAGE": {
name: "存储",
explain: "把图片保存到相册等相关功能"
}
}
/**
* 权限说明文字
* @param {String} = permissionName 例如:ACCESS_COARSE_LOCATION
*/
const texts = (permissionName) => {
let title = "";
let content = "";
let permissionInfo = permissionEnums[permissionName] || null;
if (permissionInfo) {
const { name, explain } = permissionInfo;
title = `${name}权限使用说明`;
content = `将获取${name}权限,用于${explain}`;
} else {
title = "";
content = "";
}
return {
title,
content
}
};
/**
* 绘画顶部权限说明
* 文档地址:https://www.html5plus.org/doc/zh_cn/nativeobj.html
* @function drawView title标题,content描述使用说明
* @function hideView 隐藏顶部权限说明
*/
let view = null;
const drawView = ({ title, content }) => {
console.log("drawView方法的参数值:", title, content);
if (view || !title || !content) return; // 没有标题和内容则return出去
const { windowTop, windowWidth, statusBarHeight } = uni.getSystemInfoSync();
const topHeight = windowTop + statusBarHeight;
const distance = {
box: 10, // 盒子距离视图两边的距离
text: 20 // 文字距离视图两边的距离
}
// 标题的相关样式
const titleStyle = {
size: 16,
height: 16,
top: `${topHeight + 22}`,
color: "#000",
}
// 内容的相关样式
const contentStyle = {
size: 14,
height: 0,
top: `${parseInt(titleStyle.top) + titleStyle.height + 6}`,
color: "#656563",
}
const contentLength = content.length; // 权限说明内容文字长度
const contentWidth = windowWidth - distance.text * 2; // 内容的宽度
const contentRowCount = Math.floor(contentWidth / contentStyle.size); // 一行占几个文字
const contentRows = Math.ceil(contentLength / contentRowCount); // 当前内容占几行
contentStyle.height = contentRows * (contentStyle.size + 4); // 内容的高度
/**
* @description 计算盒子的高度
* 获取content到盒子顶部距离:parseInt(contentStyle.top) - topHeight - distance.box
* content的高度:contentStyle.height
* 获取content到盒子底部的距离:(distance.text - distance.box)
*/
const boxHeight = (parseInt(contentStyle.top) - topHeight - distance.box) + contentStyle.height + (distance.text - distance.box);
view = new plus.nativeObj.View('per-modal', {
top: '0',
left: '0',
width: '100%',
backgroundColor: 'rgba(0,0,0,0.2)'
})
view.drawRect({
color: '#fff',
radius: '5px',
}, {
top: `${topHeight + distance.box}px`,
left: `${distance.box}px`,
right: `${distance.box}px`,
height: `${boxHeight}px`
})
view.drawText(title, {
top: `${titleStyle.top}px`,
left: `${distance.text}px`,
left: `${distance.text}px`,
height: `${titleStyle.height}px`
}, {
size: `${titleStyle.size}px`,
align: "left",
color: titleStyle.color,
weight: "bold"
})
view.drawText(content, {
top: `${contentStyle.top}px`,
left: `${distance.text}px`,
right: `${distance.text}px`,
height: `${contentStyle.height}px`,
}, {
size: `${contentStyle.size}px`,
lineSpacing: "2px",
align: "left",
color: contentStyle.color,
verticalAlign: "top",
whiteSpace: "normal"
})
let timer = setTimeout(() => {
view && view.show();
clearTimeout(timer);
timer = null;
}, 200)
}
// 关闭顶部权限说明
const hideView = () => {
if (view) {
view.hide();
view = null;
}
}
// 监听权限方法
const listenerFunc = () => {
stopFunc(); // 取消所有监听方法
if (canRunListener && permissionListener) {
let permissionName = ""; // 权限名称
let hasConfirm = false; // 是否有权限弹窗(触发permissionListener.onConfirm这个回调)
canRunListener = false;
canStopListener = false;
// 监听申请系统权限
permissionListener.onRequest((e) => {
console.log("permissionListener.onRequest回调:", e);
if (Array.isArray(e) && e.length > 0) {
const stringToArray = e[0].split(".");
permissionName = stringToArray[stringToArray.length - 1];
console.log("权限名称:", permissionName);
}
});
// 监听弹出系统权限授权框
permissionListener.onConfirm((e) => {
console.log("permissionListener.onConfirm回调:", e);
hasConfirm = true;
if (permissionName) {
drawView(texts(permissionName));
}
});
// 监听权限申请完成
permissionListener.onComplete((e) => {
console.log("permissionListener.onComplete回调:", e);
// e.length === 0:权限列表无值,则不继续做相对逻辑
if (e.length === 0) return;
let name = ""; // 权限名称
let explain = ""; // 权限说明
if (permissionName && permissionEnums[permissionName]) {
name = permissionEnums[permissionName].name;
explain = permissionEnums[permissionName].explain;
}
const Manifest = plus.android.importClass("android.Manifest");
const MainActivity = plus.android.runtimeMainActivity();
const permissionStatus = MainActivity.checkSelfPermission(Manifest.permission[permissionName]);
console.log("当前权限状态:", permissionStatus);
/**
* @description 永久拒绝该权限,则引导用户前往设置页
* permissionStatus != 0:权限状态是拒绝
* !hasConfirm:没有permissionListener.onConfirm这个回调
*/
if (permissionStatus != 0 && !hasConfirm && name && explain) {
uni.showModal({
title: "温馨提示",
content: `开启${name}权限后,才能${explain}`,
showCancel: true,
confirmText: "去设置",
success: (res) => {
if (res.confirm) {
uni.openAppAuthorizeSetting();
}
}
})
return;
}
canStopListener = true;
hideView();
});
}
}
// 取消所有监听方法
const stopFunc = () => {
if (canStopListener && permissionListener) {
console.log("执行permissionListener.stop()方法");
canRunListener = true;
hideView();
permissionListener.stop();
}
}
let exportObj = null;
if (permissionListener) {
exportObj = {
listenerFunc,
stopFunc
};
} else {
exportObj = null;
}
export default exportObj;
// #endif
App.vue
/**
* @author cai
* @time 2024-07-24 10:52
*/
import permissionListener from "@/utils/permissionListener.js";
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
permissionListener && permissionListener.listenerFunc();
console.log('App Show')
},
onHide: function() {
permissionListener && permissionListener.stopFunc();
console.log('App Hide')
}
}
↓↓↓ 各位大佬点点赞
文章不再更新,请前往插件市场下载使用,插件地址:https://ext.dcloud.net.cn/plugin?id=22675
安卓离线打包(本地打包)需要添加一些sdk,具体看这篇文章https://ask.dcloud.net.cn/article/41380
更新时间:2024-08-12 14:29
旧版时间:2024-08-12 14:06
测试部分,可能有bug
目前发现bug未解决
1、保存代码,代码热更新后,触发监听会报Error: [JS Framework] Failed to receiveTasks, instance (1) is not available.这个错误,重新编译正常
模拟器效果截图
permissionListener.js
/**
* @author cai
* @time 2024-08-12 14:29
* uni.createRequestPermissionListener()
* 文档地址:https://uniapp.dcloud.net.cn/api/system/create-request-permission-listener.html
* 注意:HBuilderX (4.0+) android 平台支持;HBuilderX 4.01 Vue2项目需要使用自定义基座测试监听权限申请的功能,标准基座暂不支持测试。
*/
// #ifndef APP
export default null;
// #endif
// #ifdef APP
const { osName } = uni.getSystemInfoSync();
let permissionListener = null;
// 是安卓平台,同时有uni.createRequestPermissionListener这个api
if (osName === "android" && uni.createRequestPermissionListener) {
permissionListener = uni.createRequestPermissionListener();
}
let canRunListener = true; // 是否可以执行所有监听方法
let canStopListener = true; // 是否可以执行取消所有监听方法,避免唤起权限会触发App.vue的onHide生命周期
const permissionEnums = {
"ACCESS_COARSE_LOCATION": {
name: "定位",
explain: "展示附近店铺、填写收货地址等相关功能"
},
"ACCESS_FINE_LOCATION": {
name: "定位",
explain: "展示附近店铺、填写收货地址等相关功能"
},
"READ_EXTERNAL_STORAGE": {
name: "存储",
explain: "上传图片、上传视频等相关功能"
},
"CAMERA": {
name: "相机",
explain: "扫二维码、拍摄图片等相关功能"
},
"WRITE_EXTERNAL_STORAGE": {
name: "存储",
explain: "把图片保存到相册等相关功能"
}
}
/**
* 权限说明文字
* @param {String} = permissionName 例如:ACCESS_COARSE_LOCATION
*/
const texts = (permissionName) => {
let title = "";
let content = "";
let permissionInfo = permissionEnums[permissionName] || null;
if (permissionInfo) {
const { name, explain } = permissionInfo;
title = `${name}权限使用说明`;
content = `将获取${name}权限,用于${explain}`;
} else {
title = "";
content = "";
}
return {
title,
content
}
};
/**
* 绘画顶部权限说明
* 文档地址:https://www.html5plus.org/doc/zh_cn/nativeobj.html
* @function drawView title标题,content描述使用说明
* @function hideView 隐藏顶部权限说明
*/
let view = null;
const drawView = ({ title, content }) => {
console.log("drawView方法的参数值:", title, content);
if (view || !title || !content) return; // 没有标题和内容则return出去
const { windowTop, windowWidth, statusBarHeight } = uni.getSystemInfoSync();
const topHeight = windowTop + statusBarHeight;
const distance = {
box: 10, // 盒子距离视图两边的距离
text: 20 // 文字距离视图两边的距离
}
// 标题的相关样式
const titleStyle = {
size: 16,
height: 16,
top: `${topHeight + 22}`,
color: "#000",
}
// 内容的相关样式
const contentStyle = {
size: 14,
height: 0,
top: `${parseInt(titleStyle.top) + titleStyle.height + 6}`,
color: "#656563",
}
const contentLength = content.length; // 权限说明内容文字长度
const contentWidth = windowWidth - distance.text * 2; // 内容的宽度
const contentRowCount = Math.floor(contentWidth / contentStyle.size); // 一行占几个文字
const contentRows = Math.ceil(contentLength / contentRowCount); // 当前内容占几行
contentStyle.height = contentRows * (contentStyle.size + 4); // 内容的高度
/**
* @description 计算盒子的高度
* 获取content到盒子顶部距离:parseInt(contentStyle.top) - topHeight - distance.box
* content的高度:contentStyle.height
* 获取content到盒子底部的距离:(distance.text - distance.box)
*/
const boxHeight = (parseInt(contentStyle.top) - topHeight - distance.box) + contentStyle.height + (distance.text - distance.box);
view = new plus.nativeObj.View('per-modal', {
top: '0',
left: '0',
width: '100%',
backgroundColor: 'rgba(0,0,0,0.2)'
})
view.drawRect({
color: '#fff',
radius: '5px',
}, {
top: `${topHeight + distance.box}px`,
left: `${distance.box}px`,
right: `${distance.box}px`,
height: `${boxHeight}px`
})
view.drawText(title, {
top: `${titleStyle.top}px`,
left: `${distance.text}px`,
left: `${distance.text}px`,
height: `${titleStyle.height}px`
}, {
size: `${titleStyle.size}px`,
align: "left",
color: titleStyle.color,
weight: "bold"
})
view.drawText(content, {
top: `${contentStyle.top}px`,
left: `${distance.text}px`,
right: `${distance.text}px`,
height: `${contentStyle.height}px`,
}, {
size: `${contentStyle.size}px`,
lineSpacing: "2px",
align: "left",
color: contentStyle.color,
verticalAlign: "top",
whiteSpace: "normal"
})
let timer = setTimeout(() => {
view && view.show();
clearTimeout(timer);
timer = null;
}, 200)
}
// 关闭顶部权限说明
const hideView = () => {
if (view) {
view.hide();
view = null;
}
}
// 监听权限方法
const listenerFunc = () => {
stopFunc(); // 取消所有监听方法
if (canRunListener && permissionListener) {
let permissionName = ""; // 权限名称
let hasConfirm = false; // 是否有权限弹窗(触发permissionListener.onConfirm这个回调)
canRunListener = false;
canStopListener = false;
// 监听申请系统权限
permissionListener.onRequest((e) => {
console.log("permissionListener.onRequest回调:", e);
if (Array.isArray(e) && e.length > 0) {
const stringToArray = e[0].split(".");
permissionName = stringToArray[stringToArray.length - 1];
console.log("权限名称:", permissionName);
}
});
// 监听弹出系统权限授权框
permissionListener.onConfirm((e) => {
console.log("permissionListener.onConfirm回调:", e);
hasConfirm = true;
if (permissionName) {
drawView(texts(permissionName));
}
});
// 监听权限申请完成
permissionListener.onComplete((e) => {
console.log("permissionListener.onComplete回调:", e);
// e.length === 0:权限列表无值,则不继续做相对逻辑
if (e.length === 0) return;
let name = ""; // 权限名称
let explain = ""; // 权限说明
if (permissionName && permissionEnums[permissionName]) {
name = permissionEnums[permissionName].name;
explain = permissionEnums[permissionName].explain;
}
const Manifest = plus.android.importClass("android.Manifest");
const MainActivity = plus.android.runtimeMainActivity();
const permissionStatus = MainActivity.checkSelfPermission(Manifest.permission[permissionName]);
console.log("当前权限状态:", permissionStatus);
/**
* @description 永久拒绝该权限,则引导用户前往设置页
* permissionStatus != 0:权限状态是拒绝
* !hasConfirm:没有permissionListener.onConfirm这个回调
*/
if (permissionStatus != 0 && !hasConfirm && name && explain) {
uni.showModal({
title: "温馨提示",
content: `开启${name}权限后,才能${explain}`,
showCancel: true,
confirmText: "去设置",
success: (res) => {
if (res.confirm) {
uni.openAppAuthorizeSetting();
}
}
})
return;
}
canStopListener = true;
hideView();
});
}
}
// 取消所有监听方法
const stopFunc = () => {
if (canStopListener && permissionListener) {
console.log("执行permissionListener.stop()方法");
canRunListener = true;
hideView();
permissionListener.stop();
}
}
let exportObj = null;
if (permissionListener) {
exportObj = {
listenerFunc,
stopFunc
};
} else {
exportObj = null;
}
export default exportObj;
// #endif
App.vue
/**
* @author cai
* @time 2024-07-24 10:52
*/
import permissionListener from "@/utils/permissionListener.js";
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
permissionListener && permissionListener.listenerFunc();
console.log('App Show')
},
onHide: function() {
permissionListener && permissionListener.stopFunc();
console.log('App Hide')
}
}
↓↓↓ 各位大佬点点赞
收起阅读 »小程序开发需求,找公司或工作室首次开发和长期维护
微信小程序开发需求,前端+运营端+服务器端
前端uniapp,运营管理端H5, 后端uniCloud/或其他nodejs框架
找公司或工作室首次开发,前端页面完成了大概70%,需要按照功能/页面细化及对接后端,并完成未完成的页面部分。后端未有,可使用uniCloud-admin。
首次开发后,可合作长期维护迭代,因此需要稳定的公司或者工作室。
具体需求见附件。
有合适的公司或工作室请留下联系方式或email到618457@qq.com。
微信小程序开发需求,前端+运营端+服务器端
前端uniapp,运营管理端H5, 后端uniCloud/或其他nodejs框架
找公司或工作室首次开发,前端页面完成了大概70%,需要按照功能/页面细化及对接后端,并完成未完成的页面部分。后端未有,可使用uniCloud-admin。
首次开发后,可合作长期维护迭代,因此需要稳定的公司或者工作室。
具体需求见附件。
有合适的公司或工作室请留下联系方式或email到618457@qq.com。
android 和 ios app 打开唤起支付宝支付就差个s,无语...
var alipay=this.iosOrAndroid()?'alipay':'alipays'
plus.runtime.openURL(alipay+"://platformapi/startapp?appId=20000067&url=" +
encodeURIComponent(this.shareUrl('/pages/index/payMember1?record_id='+res.data.record_id+'&level='+this.parmar.level+'&days='+this.parmar.days))) var alipay=this.iosOrAndroid()?'alipay':'alipays'
plus.runtime.openURL(alipay+"://platformapi/startapp?appId=20000067&url=" +
encodeURIComponent(this.shareUrl('/pages/index/payMember1?record_id='+res.data.record_id+'&level='+this.parmar.level+'&days='+this.parmar.days))) 收起阅读 »











