master
LiuJiaNan 2025-01-10 17:23:07 +08:00
commit d28fc10e38
160 changed files with 28276 additions and 0 deletions

View File

@ -0,0 +1,138 @@
所有交互调用失败原生那边进行toast提示不用进行详细说明原因 提示下XX调用获取失败
泛型中string[`'|'']的是数组 `'|''是字符串
所有的方法名字和参数名字都是驼峰第一个字母小写Word可能存在问题自动把第一个字母转成了大写
页面全屏可写,到电池状态栏的位置
单位size为kb宽高为px时间为秒
扫码
scanCode
入参Object<{onlyFromCamera:boolean}>
onlyFromCamera是否能从相册选择默认传falsefalse为能从相册选择
返回值Object<result:string>
result扫码内容
获取设备信息
getSystemInfo
返回值Object<{MEID:string,IMEI:string,deviceBrand:string,osName:string,deviceType:string}>
deviceBrand手机型号 osName操作系统 deviceType设备类型平板、手机
说明:还有其他常用的值也加一下
获取电池状态栏的高度
getStatusBarHeight
返回值object<{height:number,bottomSafeHeight:number}>
height电池状态栏高度 bottomSafeHeight苹果手机底部安全区域高度
获取消息推送的CID
getPushClientId
返回值object<{CID:string}>
拍照
camera
返回值Object<{path:string,size:number}>
path路径 size大小kb
录制视频时长最大60秒
recordVideo
返回值Object<{path:string,size:number,duration:number,height:number,width:number}>
path路径 size大小kb duration时长 heightpx widthpx
相册,选择图片和视频
chooseMedia
入参Object<{type:string[`image','video'],count:number,multiple:boolean}>
type选择的类型默认传image count数量默认传1 multiple是否多选默认传true
返回值Object<{path:string,size:number,duration:number,height:number,width:number,type:string}>[]
path路径 size大小kb duration时长只有视频存在图片的话返回0 heightpx widthpx type类型文件类型JPG、png
唤起指定摄像头
activateCamera
入参object<{camera:'back'|'front',interval:number}>
camera使用哪个摄像头默认传front interval每个几秒进行拍照默认传3000单位毫秒
说明不能选择相册和切换摄像头每隔几秒定时拍照返回照片不保存到相册拍照的时候去掉声音需要原生写页面中间画一个宽高相对于屏幕宽度80%的圆形圆形外面的不显示模拟人脸验证圆形上面加一行字通过activateCameraSetMessage调用关闭通过activateCameraClose调用
获取activateCamera返回值
activateCameraGetImage
返回值Object<{path:string,size:number}>[]
path沙盒路径 size大小kb
说明:会多次调用,每次调用返回上一次调用到这次调用之间的所有照片
关闭activateCamera交互调用
activateCameraClose
实时更改activateCamera圆形上面的字
activateCameraSetMessage
入参object<{message:string}>
说明:会多次调用,每次调用都需要更改文字
对接各个手机厂商消息推送
需要对接的手机厂商华为、魅族、OPOO、VIVO、荣耀、小米
手机桌面数字角标
setBadgeNumber
入参object<{count:number}>
count显示的数字
定位
getLocation
入参object<{type:'wgs84'|'gcj02',geocode:boolean}>
type使用的定位类型默认传gcj02 geocode是否解析地址信息默认传false
返回值Object<{longitude: string,latitude: string,address: {country: string,province: string,city: string,district: string,town: string,street: string,streetNumber: string,poiName: string,postalCode: string,cityCode: string}}>
longitude经度 latitude纬度 address地址信息country国家 province省 city市 district区 town镇 street街道 streetNumber街道门牌号 poiNamePOI信息 postalCode邮编 cityCode城市代码
说明无法获取到的返回空字符串gcj02的定位SDK使用百度
查询是否有权限
getPermission
入参object<{type:'camera'|'audio'|'location'|'album',title:string,describe:string}>
type需要查询的权限camera相机、audio录音、location定位、album相册 title提示的标题 describe提示的内容
返回值Object<{code:number}>
code是否有权限1已经授权2拒绝授权
说明:查询是否有当前权限,没有的话弹窗询问是否同意获取此权限,拒绝无法再次进行弹窗时跳转到系统设置让用户手动授权,还有一些常用的也加一下
打开浏览器
openBrowser
入参object<{url:string}>
url打开的网址
打开第三方app预览附件
previewFile
入参object<{url:string}>
url文件地址文件服务器上的地址
H5部署地址通过请求获取
接口地址https://pm.qhdsafety.com/zy-projectManage/project/getH5?CODE=app
返回值:{"result":"success","url":"https://jpfz.qhdsafety.com/demo"}
版本更新
updateVersion
说明有新版本进行弹窗提示更新需要原生写弹窗每次打开app进行一次版本查询手动查询的时候没有新版本原生进行toast提示当前已经是最新版本当前版本号XXX
接口地址https://pm.qhdsafety.com/zy-projectManage/projectDetails/findLastVersion?code=app&type=Android
type Android和IOS
返回值:{"result":"success","file":"123232.apk","version":"1.0"}
ios AppStore地址https://apps.apple.com/app/id6469101762
关闭app
quitApp
17.选择文件
chooseFile
入参object<{type:'doc'|'docx'|'xls'|'xlsx'|'pdf'|''}>
type选择的文件类型
返回值object<{path:string,type:string,size:number}>
path路径 size大小kb type文件类型
当前网络状态
getNetworkType
返回值object<{networkType:string}>wifi、2g、3g、4g、5g、unknown、none
说明:原生那边进行实时网络状态监测,没有网络的时候进行提示
当前app版本
getVersion
返回值object<{version:string
version当前版本
app隐藏和显示
appShow不通过桥文件调用js写全局方法挂载到window上
appHide不通过桥文件调用js写全局方法挂载到window上
切换页面
push下一页
入参object<url:string,params:object>
url页面路径 params携带的参数对象格式
pop上一页
入参object<delta:number,params:object>
delta回退几级默认一级不进行传值 params携带的参数对象格式
reLaunch关闭所有页面打开新页面
入参object<url:string,params:object>
url页面路径 params携带的参数对象格式
说明:把路由所有堆全部清除,只保留跳转过去这个路由
redirect重定向页面
入参object<url:string,params:object>
url页面路径 params携带的参数对象格式
说明abc三个页面在b页面调用跳转到cb页面在路由堆里删除掉返回的时候直接回到a
pageShow当前页面显示不通过桥文件调用js在需要监听的页面的写全局方法挂载到window上
pageHide当前页面隐藏不通过桥文件调用js在需要监听的页面的写全局方法挂载到window上
initData获取路由参数不通过桥文件调用js在需要监听的页面的写全局方法挂载到window上

19
Android/Android.docx Normal file
View File

@ -0,0 +1,19 @@
安卓打包
1
2
别名和密码
keyAlias 'qa'keyPassword '123456'storePassword '123456'
换名字
换图标
替换这个图片

BIN
Android/qa.zip Normal file

Binary file not shown.

4
demo/.env Normal file
View File

@ -0,0 +1,4 @@
VITE_BASE_URL=1
VITE_PROXY=/api
VITE_FILE_PATH=1

1
demo/.env.development Normal file
View File

@ -0,0 +1 @@
VITE_BASE=/

1
demo/.env.production Normal file
View File

@ -0,0 +1 @@
VITE_BASE=/demo

4
demo/.eslintignore Normal file
View File

@ -0,0 +1,4 @@
public
dist
package.json
!.prettierrc.cjs

62
demo/.eslintrc.cjs Normal file
View File

@ -0,0 +1,62 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
"plugin:vue/vue3-essential",
"standard",
"@vue/prettier",
"eslint:recommended",
],
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
plugins: ["vue"],
rules: {
"no-console": "warn",
"vue/multi-word-component-names": "off",
"vue/no-v-html": "off",
"vue/require-default-prop": "off",
"camelcase": "off",
"eqeqeq": "error",
"vue/no-template-shadow": "error",
"vue/attribute-hyphenation": "error",
"vue/html-end-tags": "error",
"vue/eqeqeq": "error",
"vue/component-name-in-template-casing": ["error", "kebab-case"],
"vue/enforce-style-attribute": [
"error",
{ "allow": ["scoped", "module"] }
],
"vue/v-on-event-hyphenation": ["error", "always",{
"autofix": true,
}],
"vue/require-explicit-emits": "error",
"no-restricted-syntax": [
"error",
{
"selector": "VariableDeclarator[id.name='pd']",
"message": "不允许使用 pd请改用有语义化的变量名"
},
{
"selector": "ObjectExpression > Property[key.name='pd']",
"message": "不允许使用 pd请改用有语义化的变量名"
},
],
"no-unused-vars": [
"error",
{ vars: "all", args: "after-used", ignoreRestSiblings: false }
],
"linebreak-style": ["off",'windows'],
"no-restricted-properties": [
"error",
{ "object": "Object", "property": "assign"}
],
},
globals: {
defineOptions: "readonly",
},
};

24
demo/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

4
demo/.prettierrc.cjs Normal file
View File

@ -0,0 +1,4 @@
module.exports = {
extends: ["@vue/prettier", "plugin:prettier/recommended"],
endOfLine: "crlf",
};

7
demo/README.md Normal file
View File

@ -0,0 +1,7 @@
# Vue 3 + Vite
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).

16
demo/index.html Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />
<title>demo</title>
</head>
<body>
<div id="app"></div>
<noscript>
<strong>很抱歉如果没有启用JavaScript网站无法正常工作请启用JavaScript使其正常工作。</strong>
</noscript>
<script type="module" src="/src/main.js"></script>
</body>
</html>

10
demo/jsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
]
}
}
}

10431
demo/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

54
demo/package.json Normal file
View File

@ -0,0 +1,54 @@
{
"name": "demo",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint --ext .js,.vue --fix src .prettierrc.cjs"
},
"dependencies": {
"@vant/weapp": "^1.11.7",
"@vueuse/core": "^10.11.1",
"animate.css": "^4.1.1",
"axios": "^1.7.4",
"dayjs": "^1.11.12",
"lodash-es": "^4.17.21",
"mitt": "^3.0.1",
"normalize.css": "^8.0.1",
"pinia": "^2.2.1",
"pinia-plugin-persistedstate": "^3.2.1",
"qs": "^6.13.0",
"throttle-debounce": "^5.0.2",
"vant": "^4.9.4",
"vue": "^3.4.37",
"vue-router": "^4.4.3"
},
"devDependencies": {
"@types/node": "^22.3.0",
"@vitejs/plugin-vue": "^5.1.2",
"@vue/eslint-config-prettier": "^9.0.0",
"autoprefixer": "^10.4.20",
"cnjm-postcss-px-to-viewport": "^1.0.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-n": "^16.6.2",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-vue": "^9.27.0",
"prettier": "^3.3.3",
"sass": "^1.77.8",
"unplugin-auto-import": "^0.18.2",
"unplugin-vue-components": "^0.27.4",
"vconsole": "^3.15.1",
"vite": "^5.4.0",
"vite-plugin-checker": "^0.7.2",
"vite-plugin-enhance-log": "^0.6.2",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-remove-console": "^2.2.0",
"vue-eslint-parser": "^9.4.3"
}
}

30
demo/postcss.config.cjs Normal file
View File

@ -0,0 +1,30 @@
const path = require('path')
module.exports = {
plugins: {
autoprefixer: {
overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 8', '> 1%'],
grid: true,
},
"cnjm-postcss-px-to-viewport": {
unitToConvert: "px", // 要转化的单位
viewportWidth: 750, // UI设计稿的宽度
unitPrecision: 6, // 转换后的精度,即小数点位数
propList: ["*"], // 指定转换的css属性的单位*代表全部css属性的单位都进行转换
viewportUnit: "vw", // 指定需要转换成的视窗单位默认vw
fontViewportUnit: "vw", // 指定字体需要转换成的视窗单位默认vw
selectorBlackList: [".ignore", ".hairlines"],
minPixelValue: 1, // 默认值1小于或等于1px则不进行转换
mediaQuery: true, // 是否在媒体查询的css代码中也进行转换默认false
replace: true, // 是否转换后直接更换属性值
exclude: [], // 设置忽略文件,用正则做目录名匹配
landscape: false, // 是否处理横屏情况
// 如果没有使用其他的尺寸来设计下面这个方法可以不需要比如vant是375的
customFun: ({ file }) => {
// 这个自定义的方法是针对处理vant组件下的设计稿为375问题
return path.join(file).includes(path.join("node_modules", "vant"))
? 375
: 750;
},
},
},
};

1
demo/public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

68
demo/src/App.vue Normal file
View File

@ -0,0 +1,68 @@
<template>
<suspense>
<template #default>
<div>
<van-overlay :show="miscellaneousStore.getLoading">
<van-loading color="#fff" size="24px" vertical>加载中...</van-loading>
</van-overlay>
<router-view />
</div>
</template>
<template #fallback>
<div>加载中...</div>
</template>
</suspense>
</template>
<script setup>
import { useRoute } from "vue-router";
import { watchEffect } from "vue";
import { useMiscellaneousStore } from "@/pinia/miscellaneous.js";
import { systemInfoStore } from "@/pinia/systemInfo.js";
import { useNativeGetStatusBarHeight } from "@/assets/js/useNative.js";
const store = systemInfoStore();
const miscellaneousStore = useMiscellaneousStore();
const route = useRoute();
watchEffect(() => {
window.document.title = route.meta.title;
});
window.appShow = () => {
console.log("app显示了");
};
window.appHide = () => {
console.log("app隐藏了");
};
const getBarHeight = () => {
useNativeGetStatusBarHeight({}, (data) => {
console.log(data, "电池栏高度");
store.setSystemInfo(data.height, "batteryBarsHeight");
if (data.bottomSafeHeight) {
store.setSystemInfo(data.bottomSafeHeight, "bottomSafeHeight");
}
});
};
getBarHeight();
</script>
<style scoped lang="scss">
:root {
--van-uploader-size: 146px !important;
--van-button-mini-padding: 0 20px !important;
--van-nav-bar-z-index: 9 !important;
--van-nav-bar-background: rgb(51, 119, 255) !important;
--van-nav-bar-title-text-color: var(--van-white) !important;
--van-nav-bar-icon-color: var(--van-white) !important;
--van-nav-bar-text-color: var(--van-white) !important;
}
#app {
background-color: #fafafa;
min-height: 100vh;
font-size: 30px;
}
.van-loading {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>

View File

@ -0,0 +1,26 @@
* {
box-sizing: border-box;
}
body {
overflow: hidden;
-webkit-overflow-scrolling: touch;
}
// 5
// 使1使flexmin-width: 0;
@for $i from 1 through 5 {
.line-#{$i} {
@if $i == 1 {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
} @else {
display: -webkit-box !important;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
-webkit-line-clamp: $i;
-webkit-box-orient: vertical !important;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1,2 @@
import mitt from "mitt";
export default mitt();

View File

@ -0,0 +1,34 @@
const SHIP_NATIVE = function () {};
SHIP_NATIVE.prototype.version = "0.0.1";
const clearWindowsEnv = (callback) => {
setTimeout(() => {
delete window[callback];
}, 10);
};
const sendNative = function (name, params, callback) {
const paramsToJson = JSON.stringify({
name,
params,
});
if (window.SHIP_ENV === "SHIP_IOS") {
const pre = "SHIP_IOS_CALLBACK_";
window[pre + name] = (data) => {
callback(data || null);
clearWindowsEnv(pre + name);
};
window.webkit.messageHandlers.SHIP_IOS.postMessage(paramsToJson);
}
if (window.SHIP_ENV === "SHIP_Android") {
const callbackName = "SHIP_ANDROID_CALLBACK_" + name;
window[callbackName] = (data) => {
callback(JSON.parse(data));
clearWindowsEnv(callbackName);
};
window.SHIP_NATIVE_ANDROID.postMessageAndroid(paramsToJson, callbackName);
}
};
SHIP_NATIVE.prototype.requset = function (name, params, callback) {
sendNative(name, params, callback);
};
const SHIP = new SHIP_NATIVE();
window.SHIP_NATIVE = SHIP;

View File

@ -0,0 +1,86 @@
import {
useNativePopPage,
useNativePushPage,
useNativeRedirectPage,
useNativeReLaunchPage,
} from "@/assets/js/useNative.js";
import router from "@/router/index.js";
function getNativeUrl(to, options) {
let url =
window.location.origin +
(import.meta.env.VITE_BASE || "") +
(window.location.hash ? "/#" : "") +
to.url;
if (!options.useNativeParams) {
const { href } = router.resolve({
path: to.url,
query: to.params || {},
});
url = window.location.origin + (import.meta.env.VITE_BASE || "") + href;
}
return url;
}
export function push(
to,
options = {
useNativeParams: false,
},
) {
if (window.SHIP_ENV === "SHIP_IOS" || window.SHIP_ENV === "SHIP_Android") {
useNativePushPage({
url: getNativeUrl(to, options),
params: to.params || {},
});
} else {
router.push({
path: to.url,
query: to.params || {},
});
}
}
export function pop(to = {}) {
if (window.SHIP_ENV === "SHIP_IOS" || window.SHIP_ENV === "SHIP_Android") {
useNativePopPage({ delta: to.delta || 1, params: to.params || {} });
} else {
console.warn("H5环境无法携带参数");
router.go(-(to.delta || 1));
}
}
export function redirect(
to,
options = {
useNativeParams: false,
},
) {
if (window.SHIP_ENV === "SHIP_IOS" || window.SHIP_ENV === "SHIP_Android") {
useNativeRedirectPage({
url: getNativeUrl(to, options),
params: to.params || {},
});
} else {
router.replace({
path: to.url,
query: to.params || {},
});
}
}
export function reLaunch(
to,
options = {
useNativeParams: false,
},
) {
if (window.SHIP_ENV === "SHIP_IOS" || window.SHIP_ENV === "SHIP_Android") {
useNativeReLaunchPage({
url: getNativeUrl(to, options),
params: to.params || {},
});
} else {
console.warn("H5环境没有reLaunch使用replace替代");
router.replace({
path: to.url,
query: to.params || {},
});
}
}

View File

@ -0,0 +1,156 @@
// 定位
export function useNativeGetLocation(options, callback) {
window.SHIP_NATIVE.requset(
"getLocation",
{ type: "gcj02", geocode: false, ...options },
(data) => {
callback && callback(data);
},
);
}
// 扫码
export function useNativeScanCode(
options = { onlyFromCamera: false },
callback,
) {
window.SHIP_NATIVE.requset("scanCode", { ...options }, (data) => {
callback && callback(data);
});
}
// 获取设备信息
export function useNativeSystemInfo(options = {}, callback) {
window.SHIP_NATIVE.requset("getSystemInfo", { ...options }, (data) => {
callback && callback(data);
});
}
// 获取电池状态栏的高度
export function useNativeGetStatusBarHeight(options = {}, callback) {
window.SHIP_NATIVE.requset("getStatusBarHeight", { ...options }, (data) => {
callback && callback(data);
});
}
// 获取消息推送的CID
export function useNativePushClientId(options = {}, callback) {
window.SHIP_NATIVE.requset("getPushClientId", { ...options }, (data) => {
callback && callback(data);
});
}
// 拍照
export function useNativeCamera(options = {}, callback) {
window.SHIP_NATIVE.requset("camera", { ...options }, (data) => {
callback && callback(data);
});
}
// 录制视频
export function useNativeRecordVideo(options = {}, callback) {
window.SHIP_NATIVE.requset("recordVideo", { ...options }, (data) => {
callback && callback(data);
});
}
// 从相册选择图片和视频
export function useNativeChooseMedia(options = {}, callback) {
window.SHIP_NATIVE.requset(
"chooseMedia",
{ type: ["image"], count: 1, multiple: true, ...options },
(data) => {
callback && callback(data);
},
);
}
// 唤起指定的摄像头
export function useNativeActivateCamera(options = {}, callback) {
window.SHIP_NATIVE.requset(
"activateCamera",
{
camera: "front",
interval: 3000,
...options,
},
(data) => {
callback && callback(data);
},
);
}
// 关闭唤起指定的摄像头
export function useNativeActivateCameraClose() {
window.SHIP_NATIVE.requset("activateCameraClose");
}
// 更改唤起指定的摄像头上面的文字
export function useNativeActivateCameraSetMessage(options = {}) {
window.SHIP_NATIVE.requset("activateCameraSetMessage", { ...options });
}
// 获取权限,查询是否有权限
export function useNativeGetPermission(options = {}, callback) {
window.SHIP_NATIVE.requset("getPermission", { ...options }, (data) => {
callback && callback(data);
});
}
// 打开浏览器
export function useNativeOpenBrowser(options = {}) {
window.SHIP_NATIVE.requset("openBrowser", { ...options });
}
// 通过使用第三方 app 的方式预览附件文件
export function useNativePreviewFile(options = {}) {
window.SHIP_NATIVE.requset("previewFile", { ...options });
}
// 选择文件
export function useNativeChooseFile(options = {}, callback) {
window.SHIP_NATIVE.requset("chooseFile", { ...options }, (data) => {
callback && callback(data);
});
}
// 获取当前网络状态
export function useNativeGetNetworkType(options = {}, callback) {
window.SHIP_NATIVE.requset("getNetworkType", { ...options }, (data) => {
callback && callback(data);
});
}
// 获取app版本
export function useNativeGetVersion(options = {}, callback) {
window.SHIP_NATIVE.requset("getVersion", { ...options }, (data) => {
callback && callback(data);
});
}
// 跳转到下一页
export function useNativePushPage(options = {}) {
window.SHIP_NATIVE.requset("push", { ...options });
}
// 跳转到上一页
export function useNativePopPage(options = {}) {
window.SHIP_NATIVE.requset("pop", { delta: 1, ...options });
}
// 关闭所有页面,打开新页面
export function useNativeReLaunchPage(options = {}) {
window.SHIP_NATIVE.requset("reLaunch", { ...options });
}
// 重定向页面
export function useNativeRedirectPage(options = {}) {
window.SHIP_NATIVE.requset("redirect", { ...options });
}
// 版本更新
export function getUpdateVersion(options = {}) {
window.SHIP_NATIVE.requset("updateVersion", { ...options });
}
// 退出APP
export function useNativeQuitApp() {
window.SHIP_NATIVE.requset("quitApp");
}

View File

@ -0,0 +1,3 @@
<template>
<router-view></router-view>
</template>

View File

@ -0,0 +1,50 @@
<script setup>
import { systemInfoStore } from "@/pinia/systemInfo.js";
import { storeToRefs } from "pinia";
const store = systemInfoStore();
const { systemInfo } = storeToRefs(store);
const props = defineProps({
paddingBottom: {
type: Number,
default: 0,
},
paddingTop: {
type: Number,
default: 0,
},
});
</script>
<template>
<div
class="content"
v-if="systemInfo.batteryBarsHeight > 0"
:style="{
height: `calc(100vh - ${systemInfo.batteryBarsHeight}px - ${systemInfo.bottomSafeHeight}px)`,
marginTop: systemInfo.batteryBarsHeight + 'px',
paddingBottom: props.paddingBottom + 'px',
paddingTop: props.paddingTop + 'px',
}"
>
<slot></slot>
</div>
<div
class="content-wrap"
:style="{
paddingTop: props.paddingTop + 'px',
paddingBottom: props.paddingBottom + 'px',
}"
v-else
>
<slot></slot>
</div>
</template>
<style scoped lang="scss">
.content {
overflow: scroll;
}
.content-wrap {
height: 100vh;
overflow: scroll;
}
</style>

View File

@ -0,0 +1,36 @@
<script setup>
import { systemInfoStore } from "@/pinia/systemInfo.js";
import { storeToRefs } from "pinia";
const store = systemInfoStore();
const { systemInfo } = storeToRefs(store);
</script>
<template>
<div class="head_wrap">
<div class="head">
<div :style="{ height: systemInfo.batteryBarsHeight + 'px' }"></div>
<slot></slot>
</div>
<div :style="{ height: systemInfo.batteryBarsHeight + 'px' }"></div>
</div>
</template>
<style scoped lang="scss">
.head {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 10;
> div {
background-color: #fff;
}
:deep(.van-nav-bar) {
.van-icon {
color: #222222;
}
}
}
.opacity_class {
background-color: rgba(0, 0, 0, 0) !important;
}
</style>

View File

@ -0,0 +1,110 @@
<script setup>
import { push } from "@/assets/js/useCustomRouter.js";
const props = defineProps({
list: {
type: Array,
default: () => {
return [];
},
},
});
const viewDetails = (item) => {
push({
url: "/material/material_details",
params: item,
});
};
</script>
<template>
<div class="list">
<div
class="list-item"
v-for="item in props.list"
:key="item.id"
@click="viewDetails(item)"
>
<div
class="list-item-img"
:style="{ backgroundImage: `url(${item.url})` }"
></div>
<div>
<div class="list-item-title">{{ item.name }}</div>
<div class="content">
<div class="creator" v-if="item.creator">
<img :src="item?.creator.avatar" alt="" />
<span>{{ item?.creator.name }}</span>
</div>
<div class="list-item-hot">
<van-icon name="fire" color="#FC5B5B" />
<span>热度{{ item.hot }}</span>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.list {
display: flex;
flex-wrap: wrap;
.list-item {
width: calc(50% - 18px);
margin-right: 36px;
margin-bottom: 56px;
&:nth-child(even) {
margin-right: 0;
}
.list-item-img {
width: 100%;
height: 329px;
background-repeat: no-repeat;
background-position: center center;
background-size: cover;
margin-bottom: 20px;
border-radius: 15px;
}
.list-item-title {
width: 100%;
font-weight: 800;
font-size: 32px;
color: #222222;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin-bottom: 25px;
}
.content {
display: flex;
align-items: center;
justify-content: space-between;
.creator {
display: flex;
align-items: center;
img {
width: 53px;
height: 53px;
margin-right: 10px;
border-radius: 50%;
}
span {
font-weight: 500;
font-size: 24px;
color: #999999;
}
}
}
.list-item-hot {
flex: 1;
display: flex;
justify-content: flex-end;
align-items: center;
font-weight: 500;
font-size: 24px;
color: #999999;
}
}
}
</style>

View File

@ -0,0 +1,32 @@
<script setup>
import { pop } from "@/assets/js/useCustomRouter.js";
// eslint-disable-next-line no-unused-vars
const props = defineProps({
title: {
type: String,
default: "",
},
leftArrow: {
type: Boolean,
default: false,
},
border: {
type: Boolean,
default: false,
},
});
const back = () => {
pop();
};
</script>
<template>
<van-nav-bar
:title="props.title"
:left-arrow="props.leftArrow"
:border="props.border"
@click-left="back"
/>
</template>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,45 @@
<script setup>
import { reLaunch } from "@/assets/js/useCustomRouter.js";
import { useRoute } from "vue-router";
import { systemInfoStore } from "@/pinia/systemInfo.js";
import { storeToRefs } from "pinia";
const store = systemInfoStore();
const { systemInfo } = storeToRefs(store);
const route = useRoute();
const active = route.name;
const onChange = (index) => {
console.log("关闭所有页面,打开新页面");
reLaunch({
url: `/${index}`,
});
};
</script>
<template>
<div
class="van-tab"
:style="{ paddingBottom: systemInfo.bottomSafeHeight + 'px' }"
>
<van-tabbar
v-model="active"
@change="onChange"
:safe-area-inset-bottom="true"
:fixed="false"
:border="false"
>
<van-tabbar-item name="index" icon="home-o">首页</van-tabbar-item>
<van-tabbar-item name="material" icon="apps-o">素材库</van-tabbar-item>
<van-tabbar-item name="user" icon="manager-o">我的</van-tabbar-item>
</van-tabbar>
</div>
</template>
<style scoped lang="scss">
.van-tab {
background-color: #fff;
width: 100%;
position: fixed;
left: 0;
bottom: 0;
}
</style>

View File

@ -0,0 +1,83 @@
<script setup>
import { computed } from "vue";
const props = defineProps({
list: {
type: Array,
default: () => {
return [];
},
},
current: {
type: Number,
default: 0,
},
listNum: {
type: Number,
default: 1,
},
switchName: {
type: String,
default: "name",
},
});
const emit = defineEmits(["onSwitch"]);
const switchCurrent = (name, ind) => {
emit("onSwitch", { name, ind });
};
const tabsNum = computed(() => {
return props.listNum;
});
</script>
<template>
<div class="tab-bars">
<span
v-for="(item, index) in props.list"
:key="item.name"
:class="{ active: props.current === index }"
@click="switchCurrent(item[props.switchName], index)"
>{{ item.name }}</span
>
<view class="line" :class="`line${props.current + 1}`"></view>
</div>
</template>
<style scoped lang="scss">
$num: v-bind(tabsNum);
.tab-bars {
display: flex;
flex-wrap: nowrap;
background-color: #fff;
padding: 30px 0 50px;
align-items: center;
position: relative;
span {
width: calc(100% / 3);
text-align: center;
font-weight: 500;
font-size: 27px;
color: #999999;
}
.active {
color: #fc5b5b;
font-size: 30px;
}
.line {
position: absolute;
bottom: 30px;
width: 70px;
height: 10px;
border-radius: 10px;
background-color: #fc5b5b;
transition: all 0.2s linear;
}
@for $i from 1 through 99 {
$numberWidth: 100% / $num;
$fixed: $numberWidth / 2;
$deviation: 70px / 2;
.line#{$i} {
left: calc(($numberWidth * $i - $fixed - $deviation));
}
}
}
</style>

19
demo/src/main.js Normal file
View File

@ -0,0 +1,19 @@
import { createApp } from "vue";
import "dayjs/locale/zh-cn";
import "@/assets/css/common.scss";
import "normalize.css";
import "animate.css";
import "vant/es/toast/style";
import "vant/es/dialog/style";
import "vant/es/notify/style";
import "vant/es/image-preview/style";
import App from "./App.vue";
import pinia from "./pinia";
import router from "./router";
// import vconsole from "vconsole";
import "@/assets/js/nativeBridge.js";
// eslint-disable-next-line new-cap,no-new
// new vconsole();
createApp(App).use(pinia).use(router).mount("#app");

7
demo/src/pinia/index.js Normal file
View File

@ -0,0 +1,7 @@
import { createPinia } from "pinia";
import piniaPersistedstate from "pinia-plugin-persistedstate";
const pinia = createPinia();
pinia.use(piniaPersistedstate);
export default pinia;

View File

@ -0,0 +1,19 @@
import { defineStore } from "pinia";
export const useMiscellaneousStore = defineStore("miscellaneousStore", {
state: () => ({
loading: false, // 全局加载动画
}),
getters: {
getLoading: (state) => state.loading,
},
actions: {
setLoading(loading) {
this.loading = loading;
},
},
persist: {
storage: window.sessionStorage,
paths: ["loading"],
},
});

View File

@ -0,0 +1,22 @@
import { defineStore } from "pinia";
export const systemInfoStore = defineStore("systemInfoStore", {
state: () => ({
systemInfo: {
batteryBarsHeight: 0,
bottomSafeHeight: 0,
system: {},
address: {},
},
}),
getters: {},
actions: {
setSystemInfo(data, name) {
this.systemInfo[name] = data;
},
},
persist: {
storage: window.sessionStorage,
paths: ["systemInfo"],
},
});

24
demo/src/pinia/user.js Normal file
View File

@ -0,0 +1,24 @@
import { defineStore } from "pinia";
export const useUserStore = defineStore("useUserStore", {
state: () => ({
data: {
userInfo: {},
release: [],
},
}),
getters: {},
actions: {
setUserData(data, name) {
if (name === "release") {
this.data[name].push(data);
return;
}
this.data[name] = data;
},
},
persist: {
storage: window.localStorage,
paths: ["data"],
},
});

239
demo/src/pinia/works.js Normal file
View File

@ -0,0 +1,239 @@
import { defineStore } from "pinia";
import avatar5 from "@/assets/images/avatar5.jpg";
import avatar2 from "@/assets/images/avatar2.jpg";
import avatar3 from "@/assets/images/avatar3.jpg";
import avatar4 from "@/assets/images/avatar4.jpg";
import avatar1 from "@/assets/images/avatar1.jpg";
import avatar6 from "@/assets/images/avatar6.jpg";
export const useWorksStore = defineStore("useWorksStore", {
state: () => ({
data: {
release: [
{
id: 1,
url: "https://fastly.picsum.photos/id/866/200/300.jpg?hmac=rcadCENKh4rD6MAp6V_ma-AyWv641M4iiOpe1RyFHeI",
name: "九寨沟风景",
hot: 1354,
classify: "1",
type: "2",
},
{
id: 2,
url: "https://fastly.picsum.photos/id/62/300/300.jpg?hmac=JkaOmZr8So-hvPO3Mya00sE2pvIQtLZjGMldqbPFiqc",
name: "风景",
hot: 896,
classify: "2",
type: "2",
creator: {
name: "风景一线",
avatar: avatar5,
},
},
{
id: 3,
url: "https://fastly.picsum.photos/id/858/300/300.jpg?hmac=_ejZ2a4fSroS4BO_gXXIxq7hyd0RIHSP290jgbqMO2c",
name: "都市风格",
hot: 27,
classify: "2",
type: "3",
creator: {
name: "Ammy",
avatar: avatar2,
},
},
{
id: 4,
url: "https://fastly.picsum.photos/id/472/300/300.jpg?hmac=a3XXSq9tQnozNx-l8rmPt0Y-orMbCaE2t8lMdPeyl8I",
name: "云雾",
hot: 447,
classify: "2",
type: "2",
creator: {
name: "风景一线",
avatar: avatar5,
},
},
{
id: 5,
url: "https://fastly.picsum.photos/id/72/300/300.jpg?hmac=tx7xDri-jWJWeXKZ4OwXuD9jCiWOgGtfZBNC11VAX8g",
name: "黑白",
hot: 8446,
classify: "1",
type: "3",
},
{
id: 6,
url: "https://fastly.picsum.photos/id/423/300/300.jpg?hmac=b-ZC4rgdo_5hNS1yFs9pkVkPN7Co7dJ8TBAGFCPhYCs",
name: "雪松",
hot: 21,
classify: "1",
type: "2",
},
{
id: 7,
url: "https://fastly.picsum.photos/id/730/300/300.jpg?hmac=-ZzQFNagVnAQRxKCAdznDnNils2eMzvfT7ooODMz1ws",
name: "雪中的景色",
hot: 12,
classify: "1",
type: "2",
},
{
id: 8,
url: "https://fastly.picsum.photos/id/299/300/300.jpg?hmac=AL30gVrvEQMZgWvQ53mFxMB2rRy8wNO9yDOHwEsO1k0",
name: "街道",
hot: 93,
classify: "2",
type: "1",
creator: {
name: "雾迷了风景",
avatar: avatar3,
},
},
{
id: 9,
url: "https://fastly.picsum.photos/id/808/300/300.jpg?hmac=ezNKdtmIxkOpancGBAvNd5QiBskqPBhhHN1jV9IvP0I",
name: "田地",
hot: 123,
classify: "1",
type: "2",
},
{
id: 10,
url: "https://fastly.picsum.photos/id/866/400/300.jpg?hmac=JMubLT0llOloTrCSJIptm4kmT13cmWrNcdbpI9vJwmw",
name: "雪中山峰",
hot: 53,
classify: "1",
type: "2",
},
{
id: 11,
url: "https://fastly.picsum.photos/id/461/400/300.jpg?hmac=LxC9-n5d-4ruUfj2z_sK-A1bvOrkDiWtG_V2umD0c7U",
name: "海边",
hot: 45,
classify: "1",
type: "2",
},
{
id: 12,
url: "https://fastly.picsum.photos/id/818/400/300.jpg?hmac=bmrMnmW7AtSXUVO7K5b8F4BHfX2_CSO8VR7xzjlACPo",
name: "自由女神像",
hot: 456,
classify: "1",
type: "1",
},
{
id: 13,
url: "https://fastly.picsum.photos/id/369/300/400.jpg?hmac=GTBdppLn_1mhzd23S7owmFFdI4eGewmZQB6lrPLd8lI",
name: "街道",
hot: 453,
classify: "1",
type: "1",
},
{
id: 14,
url: "https://fastly.picsum.photos/id/408/300/400.jpg?hmac=1pTfKCG8_tiCVSoQSOYZ8zlKnh2jBmhZWpf3zy9X0hc",
name: "城市",
hot: 456,
classify: "2",
type: "1",
creator: {
name: "是风景",
avatar: avatar4,
},
},
{
id: 15,
url: "https://fastly.picsum.photos/id/884/400/300.jpg?hmac=1RfUNtqPHogkhXDXPCPKjTfiNfJ_KHjx-Td_J4K8P4Q",
name: "沙漠的夜空",
hot: 45,
classify: "2",
type: "2",
creator: {
name: "Rumi",
avatar: avatar1,
},
},
{
id: 16,
url: "https://fastly.picsum.photos/id/480/300/400.jpg?hmac=TH4P4XKNsw19R2bhQAEKxMnsRSFvQPG7CTb6Jt3wbcQ",
name: "田野",
hot: 86,
classify: "2",
type: "2",
creator: {
name: "Rumi",
avatar: avatar1,
},
},
{
id: 17,
url: "https://fastly.picsum.photos/id/220/300/400.jpg?hmac=-37QrITtobNkaprlkFaWKkc4-plr-KZJu68_nM2jnBI",
name: "夜晚车站",
hot: 4556,
classify: "1",
type: "1",
},
{
id: 18,
url: "https://fastly.picsum.photos/id/241/300/400.jpg?hmac=73lq2CQa3NiMAdFOA6xPdkbwPqL6ru_koaZjarmjtoY",
name: "老照片",
hot: 452,
classify: "2",
type: "3",
creator: {
name: "旅途Φの风景",
avatar: avatar6,
},
},
{
id: 19,
url: "https://fastly.picsum.photos/id/679/300/300.jpg?hmac=sOnn1erXgFUtrEBHqNgADZqc_mVDj-eKoCtg3Rb5qmM",
name: "螺旋",
hot: 981,
classify: "2",
type: "3",
creator: {
name: "旅途Φの风景",
avatar: avatar6,
},
},
{
id: 20,
url: "https://fastly.picsum.photos/id/43/200/300.jpg?hmac=F_cVhLISpNmZ9wjirHfMJgX9rQzMYJbJE1xzfwmV36c",
name: "夜景",
hot: 8446,
classify: "2",
type: "3",
creator: {
name: "Rumi",
avatar: avatar1,
},
},
],
},
}),
getters: {},
actions: {
setShield(data, name) {
if (name === "works") {
this.data.release = this.data.release.filter(
(item) => item.id !== Number(data.id),
);
return;
}
const newItem = this.data.release.filter(
(item) => item.id === Number(data.id),
);
const list = this.data.release.filter((item) => item.classify === "2");
this.data.release = list.filter(
(item) => item.creator.name !== newItem[0].creator.name,
);
},
},
persist: {
storage: window.localStorage,
paths: ["data"],
},
});

0
demo/src/request/api.js Normal file
View File

140
demo/src/request/axios.js Normal file
View File

@ -0,0 +1,140 @@
import axios from "axios";
import router from "../router";
import QS from "qs";
import { showToast } from "vant";
import { useMiscellaneousStore } from "../pinia/miscellaneous";
const miscellaneousStore = useMiscellaneousStore();
function startLoading() {
miscellaneousStore.setLoading(true);
}
function endLoading() {
miscellaneousStore.setLoading(false);
}
axios.defaults.timeout = 1000 * 60 * 10;
// axios.defaults.withCredentials = true;
axios.interceptors.request.use(
(config) => {
if (config.method === "get" || config.method === "GET") {
if (QS.parse(config.params)?.loading !== "false") startLoading();
}
if (config.method === "post" || config.method === "POST") {
if (QS.parse(config.data)?.loading !== "false") startLoading();
}
return config;
},
(error) => Promise.reject(error)
);
axios.interceptors.response.use(
(config) => {
if (config.config.method === "get" || config.config.method === "GET") {
if (QS.parse(config.config.params)?.loading !== "false") endLoading();
}
if (config.config.method === "post" || config.config.method === "POST") {
if (QS.parse(config.config.data)?.loading !== "false") endLoading();
}
return config;
},
(error) => {
if (error && error.response) {
switch (error.response.status) {
case 0:
case 302:
endLoading();
showToast("登录失效,请重新登陆");
router.push("/login").then();
break;
case 401:
endLoading();
router.push("/login").then();
break;
default:
error.message = `连接错误${error.response.status}`;
showToast(`连接错误${error.response.status}`);
}
} else {
error.message = "连接到服务器失败";
showToast("连接到服务器失败");
}
return Promise.reject(error.message);
}
);
export function post(url, params) {
return new Promise((resolve, reject) => {
axios
.post(
url,
QS.stringify({
...params,
}),
{
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
}
)
.then((res) => {
if (res.data.result === "success" && res.data.code !== "9999") {
resolve(res.data);
} else {
showToast(res.data.msg || "系统开小差了");
reject(res.data);
}
})
.catch((err) => {
reject(err);
});
});
}
export function get(url, params) {
return new Promise((resolve, reject) => {
axios
.get(url, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
params: QS.stringify({
...params,
}),
})
.then((res) => {
if (res.data.result === "success" && res.data.code !== "9999") {
resolve(res.data);
} else {
showToast(res.data.msg || "系统开小差了");
reject(res.data);
}
})
.catch((err) => {
reject(err);
});
});
}
export function upload(url, params) {
return new Promise((resolve, reject) => {
axios
.post(url, params, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((res) => {
if (res.data.result === "success" && res.data.code !== "9999") {
resolve(res.data);
} else {
showToast(res.data.msg || "系统开小差了");
reject(res.data);
}
})
.catch((err) => {
reject(err);
});
});
}

114
demo/src/router/index.js Normal file
View File

@ -0,0 +1,114 @@
import { createRouter, createWebHashHistory } from "vue-router";
const routes = [
{ path: "/", redirect: "/index" },
{
path: "/index",
name: "index",
meta: { title: "首页", navbar: false },
component: () => import("@/views/index/index"),
},
{
path: "/platform",
name: "platform",
meta: { title: "平台素材", navbar: false },
component: () => import("@/views/platform/platform"),
},
{
path: "/expert",
name: "expert",
meta: { title: "达人分享", navbar: false },
component: () => import("@/views/expert/expert"),
},
{
path: "/login",
name: "login",
meta: { title: "登录", navbar: false },
component: () => import("@/views/login/login"),
},
{
path: "/change_password",
name: "change_password",
meta: { title: "修改密码", navbar: false },
component: () => import("@/views/change_password/change_password"),
},
{
path: "/material",
name: "material",
meta: { title: "素材列表", navbar: false },
component: () => import("@/views/material/material"),
},
{
path: "/material/material_details",
name: "material_details",
meta: { title: "素材详情", navbar: false },
component: () => import("@/views/material_details/material_details"),
},
{
path: "/my_upload",
name: "my_upload",
meta: { title: "我要上传", navbar: false },
component: () => import("@/views/my_upload/my_upload"),
},
{
path: "/user",
name: "user",
meta: { title: "个人中心", navbar: false },
component: () => import("@/views/user/user"),
},
{
path: "/user/problem_feedback",
name: "problem_feedback",
meta: { title: "问题反馈", navbar: false },
component: () => import("@/views/problem_feedback/problem_feedback"),
},
{
path: "/user/privacy_agreement",
name: "privacy_agreement",
meta: { title: "隐私协议", navbar: false },
component: () => import("@/views/privacy_agreement/privacy_agreement"),
},
{
path: "/user/user_agreement",
name: "user_agreement",
meta: { title: "用户协议", navbar: false },
component: () => import("@/views/user_agreement/user_agreement"),
},
{
path: "/user/my_release",
name: "my_release",
meta: { title: "我的发布", navbar: false },
component: () => import("@/views/my_release/my_release"),
},
{
path: "/search",
name: "search",
meta: { title: "搜索", navbar: false },
component: () => import("@/views/search/search"),
},
{
path: "/search/search_list",
name: "search_list",
meta: { title: "搜索结果", navbar: false },
component: () => import("@/views/search_list/search_list"),
},
{
path: "/cancel_account",
name: "cancel_account",
meta: { title: "申请注销账号", navbar: false },
component: () => import("@/views/cancel_account/cancel_account"),
},
];
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition;
} else {
return { left: 0, top: 0 };
}
},
});
export default router;

View File

@ -0,0 +1,67 @@
<script setup>
import { showDialog } from "vant";
import { reLaunch } from "@/assets/js/useCustomRouter.js";
import TabHeader from "@/components/head/head.vue";
import NavBar from "@/components/nav-bar/nav-bar.vue";
const onCancel = () => {
showDialog({
title: "提示",
message: "我们将在7-15工作日处理",
confirmButtonText: "确认注销",
showCancelButton: true,
})
.then(() => {
reLaunch({
url: "/login",
params: {},
});
})
.catch(() => {
console.log(222);
});
};
</script>
<template>
<div class="wrap">
<tab-header :sticky="true">
<nav-bar title="申请注销账号" :left-arrow="true" :border="false"></nav-bar>
</tab-header>
<p class="title">
为保证你的账号安全在你提交的注销申请生效前需同时满足以下条件:
</p>
<p>1.账号处于安全状态</p>
<span>账号为你本人拥有处于正常使用状态无被盗违规封禁风险</span>
<p>2.账号财产已结清交易已完成</p>
<span
>账号下所有关联业务和权益均已清空或自愿放弃所有上传作品已删除或已自愿放弃
账号对应的信息将同步注销</span
>
<p>3.账号无任何纠纷包括但不限于投诉或举报服务均已完成或已自愿放弃</p>
<van-button class="button" color="#fe5353" @click="onCancel"></van-button>
</div>
</template>
<style scoped lang="scss">
.wrap {
padding: 20px 30px;
.title {
margin-bottom: 20px;
}
p {
margin-bottom: 15px;
font-size: 30px;
line-height: 50px;
}
span {
font-size: 24px;
}
}
.button {
position: fixed;
left: 20px;
bottom: 30px;
right: 20px;
}
</style>

View File

@ -0,0 +1,173 @@
<script setup>
import { ref } from "vue";
import { showToast } from "vant";
import { push } from "@/assets/js/useCustomRouter.js";
import logo from "@/assets/images/logo.png";
import NavBar from "@/components/nav-bar/nav-bar.vue";
import { systemInfoStore } from "@/pinia/systemInfo.js";
import { storeToRefs } from "pinia";
const store = systemInfoStore();
const { systemInfo } = storeToRefs(store);
const loginData = ref({
password: "",
confirmPassword: "",
oldPassword: "",
});
const login = () => {
if (!valida()) return;
push({
url: "/login",
param: {},
});
};
const valida = () => {
if (loginData.value.oldPassword === "") {
showToast("请输入旧密码");
return false;
}
if (loginData.value.password === "") {
showToast("请输入密码");
return false;
}
if (loginData.value.confirmPassword === "") {
showToast("请再次输入密码");
return false;
}
return true;
};
</script>
<template>
<div class="login">
<div
class="head"
:style="{ paddingTop: systemInfo.batteryBarsHeight + 'px' }"
>
<nav-bar title="修改密码" :left-arrow="true" :border="false"></nav-bar>
</div>
<div class="box">
<div class="logo">
<img :src="logo" alt="" />
</div>
<div class="title">校园安全一点通</div>
</div>
<div class="login-form">
<div class="form-item">
<input
type="password"
v-model="loginData.oldPassword"
placeholder="请输入旧密码"
/>
</div>
<div class="form-item">
<input
type="password"
v-model="loginData.password"
placeholder="请输入新密码"
/>
</div>
<div class="form-item">
<input
type="password"
v-model="loginData.confirmPassword"
placeholder="再次输入新密码"
/>
</div>
<button class="submit" @click="login"></button>
</div>
</div>
</template>
<style scoped lang="scss">
.head {
:deep(.van-nav-bar) {
background-color: rgba(0, 0, 0, 0);
.van-nav-bar__title {
color: #222222;
}
.van-icon {
color: #222222;
}
}
}
.login {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
background-image: url("../../assets/images/login_bj.jpg");
background-size: cover;
background-position: top center;
}
.box {
padding-top: 124px;
.logo {
width: 116px;
height: 116px;
padding: 21px;
margin: 0 auto 36px;
border-radius: 17px;
background-color: #fff;
img {
width: 100%;
display: block;
}
}
.title {
font-size: 38px;
font-weight: bold;
text-align: center;
margin-bottom: 34px;
}
span {
display: block;
margin-bottom: 96px;
text-align: center;
font-weight: 500;
font-size: 27px;
color: #4f4e4e;
line-height: 27px;
}
}
.login-form {
border-radius: 67px 67px 0 0;
background-color: #fff;
flex: 1;
padding: 121px 92px 0;
.form-item {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 99px;
background-color: #f6f9ff;
border-radius: 50px;
margin-bottom: 75px;
padding: 0 32px 0 59px;
input {
flex: 1;
height: 100%;
background-color: rgba(0, 0, 0, 0);
border: 1px solid rgba(0, 0, 0, 0);
}
span {
width: 145px;
display: block;
font-size: 27px;
color: #fe5353;
}
}
.submit {
display: block;
width: 100%;
line-height: 99px;
background-color: #fe5353;
border-radius: 50px;
border: 1px solid #fe5353;
font-weight: bold;
font-size: 32px;
color: #ffffff;
}
}
</style>

View File

@ -0,0 +1,55 @@
<script setup>
import {computed, ref} from "vue";
import IndexList from "@/components/index-list/index-list.vue";
import Tabs from "@/components/tabs/tabs.vue";
import NavBar from "@/components/nav-bar/nav-bar.vue";
import TabHeader from "@/components/head/head.vue";
import Content from "@/components/content/content.vue";
import { useWorksStore } from "@/pinia/works.js";
import { storeToRefs } from "pinia";
const worksStore = useWorksStore();
const { data } = storeToRefs(worksStore);
const navList = [
{ name: "都市风格", id: "1" },
{ name: "唯美风景", id: "2" },
{ name: "黑白照片", id: "3" },
];
const hotList = computed(() => {
return data.value.release.filter((item) => item.classify === "2");
});
const list = ref([]);
const current = ref(0);
const switchCurrent = (option) => {
current.value = option.ind;
list.value = hotList.value.filter((item) => item.type === option.name);
};
switchCurrent({ ind: 0, name: "1" });
</script>
<template>
<div class="wrap">
<tab-header :sticky="true">
<nav-bar title="达人分享" :left-arrow="true" :border="false"></nav-bar>
<tabs
:current="current"
:list-num="3"
switch-name="id"
:list="navList"
@on-switch="switchCurrent"
></tabs>
</tab-header>
<content :padding-top="110">
<div class="list-box">
<index-list :list="list"></index-list>
</div>
</content>
</div>
</template>
<style scoped lang="scss">
.list-box {
padding: 0 26px 55px;
}
</style>

View File

@ -0,0 +1,239 @@
<template>
<div
class="wrap"
:style="{ height: `calc(100vh - ${systemInfo.bottomSafeHeight} + px)` }"
>
<div class="head">
<div class="head-nav">
<span>校园安全一点通</span>
<van-icon
name="scan"
size="22px"
@click="fnScanCode({ onlyFromCamera: true })"
/>
</div>
<span>好评如潮的素材平台3,960,000+内容等你发现</span>
<div class="search" @click="jump({ url: '/search', param: {} })">
<input type="text" readonly placeholder="搜索爆款短视频或文案教材" />
<van-icon name="search" size="20px" />
</div>
<div class="nav-list">
<div
class="nav-list-item"
v-for="item in navList"
:key="item.name"
@click="jump(item.page)"
>
<img :src="item.url" alt="" />
<span>{{ item.name }}</span>
</div>
</div>
<div class="banner">
<img
:src="item.url"
alt=""
v-for="item in bannerList"
:key="item.id"
@click="jump(item.page)"
/>
</div>
</div>
<div class="list-box">
<div class="list-box-title">
<span>平台作品</span>
<div class="more" @click="more(1)">
<span>更多</span>
<van-icon name="arrow" size="14px" color="#999999" />
</div>
</div>
<index-list :list="hotList"></index-list>
</div>
<div class="list-box">
<div class="list-box-title">
<span>达人分享</span>
<div class="more" @click="more(2)">
<span>更多</span>
<van-icon name="arrow" size="14px" color="#999999" />
</div>
</div>
<index-list :list="expert"></index-list>
</div>
</div>
<tabbar select-tab="index"></tabbar>
</template>
<script setup>
import { computed, ref } from "vue";
import { push, reLaunch } from "@/assets/js/useCustomRouter.js";
import { useNativeScanCode } from "@/assets/js/useNative.js";
import nav1 from "@/assets/images/nav_1.png";
import nav2 from "@/assets/images/nav_2.png";
import nav3 from "@/assets/images/nav_3.png";
import banner1 from "@/assets/images/banner_1.png";
import banner2 from "@/assets/images/banner_2.png";
import IndexList from "@/components/index-list/index-list";
import Tabbar from "@/components/tabbar/tabbar.vue";
import { useUserStore } from "@/pinia/user.js";
import { storeToRefs } from "pinia";
import { useWorksStore } from "@/pinia/works.js";
import { systemInfoStore } from "@/pinia/systemInfo.js";
const userStore = useUserStore();
const { data } = storeToRefs(userStore);
const store = systemInfoStore();
const { systemInfo } = storeToRefs(store);
const worksStore = useWorksStore();
const { data: worksData } = storeToRefs(worksStore);
const navList = [
{
name: "平台作品",
url: nav1,
page: { url: "/platform", params: {} },
},
{
name: "达人分享",
url: nav2,
page: { url: "/expert", params: {} },
},
{
name: "我要上传",
url: nav3,
page: { url: "/my_upload", params: {} },
},
];
const bannerList = [
{ id: 1, url: banner1, page: { url: "/material", params: {} } },
{ id: 2, url: banner2, page: { url: "/my_upload", params: {} } },
];
const scanCode = ref("");
const hotList = computed(() => {
const list = worksData.value.release.filter((item) => item.classify === "1");
return list.splice(0, 5);
});
const expert = computed(() => {
const list = worksData.value.release.filter((item) => item.classify === "2");
return list.splice(0, 5);
});
if (!data.value.userInfo.userName) {
reLaunch({
url: "/login",
});
}
const fnScanCode = (options) => {
useNativeScanCode(options, (data) => {
scanCode.value = data;
console.log(scanCode.value, "二维码");
});
};
const more = (type) => {
push({
url: "/material",
params: {
pageType: type,
},
});
};
const jump = (options) => {
push(options);
};
</script>
<style lang="scss" scoped>
.wrap {
height: 100vh;
overflow: auto;
padding-bottom: 96px;
}
.head {
padding: 120px 32px 24px 28px;
background-image: url("../../assets/images/login_bj.jpg");
background-size: cover;
background-position: top center;
> span {
display: block;
font-weight: 500;
font-size: 27px;
color: #222222;
margin: 24px 0;
}
.head-nav {
display: flex;
justify-content: space-between;
align-items: center;
span {
font-weight: 800;
font-size: 40px;
color: #222222;
}
}
.search {
height: 88px;
background-color: #ffffff;
border-radius: 44px;
border: 1px solid #ececec;
display: flex;
align-items: center;
padding: 0 36px 0 30px;
input {
border: 1px solid rgba(0, 0, 0, 0);
height: 100%;
flex: 1;
font-weight: 500;
font-size: 27px;
}
}
.nav-list {
display: flex;
align-items: center;
margin: 36px 0;
.nav-list-item {
width: calc(100% / 4);
img {
width: 100px;
display: block;
margin: 0 auto;
}
span {
font-weight: 500;
font-size: 27px;
color: #222222;
line-height: 60px;
display: block;
text-align: center;
}
}
}
.banner {
display: flex;
justify-content: space-between;
img {
width: calc(50% - 14px);
}
}
}
.list-box {
border-top: 14px solid #f3f3f3;
padding: 30px 26px 0;
.list-box-title {
display: flex;
justify-content: space-between;
margin-bottom: 45px;
> span {
font-weight: 800;
font-size: 32px;
color: #222222;
line-height: 48px;
}
.more {
display: flex;
align-items: center;
span {
font-weight: 500;
font-size: 24px;
color: #999999;
}
}
}
}
</style>

View File

@ -0,0 +1,202 @@
<script setup>
import { ref } from "vue";
import { showToast } from "vant";
import { push } from "@/assets/js/useCustomRouter.js";
import logo from "@/assets/images/logo.png";
import { useUserStore } from "@/pinia/user.js";
const userStore = useUserStore();
const loginData = ref({
userName: "",
password: "",
});
const checked = ref(false);
const login = () => {
if (!valida()) return;
push({
url: "/index",
param: {},
});
userStore.setUserData(loginData.value, "userInfo");
};
const valida = () => {
if (!checked.value) {
showToast("请先同意用户协议");
return false;
}
const merge =
/^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[0-9])\d{8}$/;
if (loginData.value.userName === "") {
showToast("请输入手机号");
return false;
}
if (
loginData.value.userName.length !== 11 ||
!merge.test(loginData.value.userName)
) {
showToast("手机号格式不正确");
return false;
}
if (loginData.value.password === "") {
showToast("请输入密码");
return false;
}
if (
loginData.value.userName !== "18700000000" &&
loginData.value.password !== "123456"
) {
showToast("用户名密码不正确");
return false;
}
return true;
};
const onAgreement = (type) => {
if (type === "user") {
push({ url: "/user/user_agreement", params: {} });
} else {
push({ url: "/user/privacy_agreement", params: {} });
}
};
</script>
<template>
<div class="login">
<div class="box">
<div class="logo">
<img :src="logo" alt="" />
</div>
<div class="title">校园安全一点通</div>
<span>未注册手机号将自动注册并登录</span>
</div>
<div class="login-form">
<div class="form-item mb75">
<input
type="text"
v-model="loginData.userName"
placeholder="请输入手机号"
/>
</div>
<div class="form-item mb50">
<input
type="password"
v-model="loginData.password"
placeholder="请输入新密码"
/>
</div>
<div class="agreement mb50">
<van-checkbox v-model="checked" checked-color="#fe5353"></van-checkbox>
<p>
我已阅读并同意<span @click="onAgreement('user')"></span
><span @click="onAgreement('privacy')"></span>
</p>
</div>
<button class="submit" @click="login"></button>
</div>
</div>
</template>
<style scoped lang="scss">
.login {
width: 100%;
height: 100vh;
//display: flex;
//flex-direction: column;
background-image: url("../../assets/images/login_bj.jpg");
background-size: cover;
background-position: top center;
padding-bottom: 60px;
overflow: auto;
}
.box {
padding-top: 224px;
.logo {
width: 116px;
height: 116px;
padding: 21px;
margin: 0 auto 36px;
border-radius: 17px;
background-color: #fff;
img {
width: 100%;
display: block;
}
}
.title {
font-size: 38px;
font-weight: bold;
text-align: center;
margin-bottom: 34px;
}
span {
display: block;
margin-bottom: 96px;
text-align: center;
font-weight: 500;
font-size: 27px;
color: #4f4e4e;
line-height: 27px;
}
}
.login-form {
border-radius: 67px 67px 0 0;
background-color: #fff;
//flex: 1;
padding: 121px 92px 100px;
.form-item {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 99px;
background-color: #f6f9ff;
border-radius: 50px;
padding: 0 32px 0 59px;
input {
flex: 1;
height: 100%;
background-color: rgba(0, 0, 0, 0);
border: 1px solid rgba(0, 0, 0, 0);
font-size: 24px;
}
span {
width: 135px;
display: block;
font-size: 27px;
color: #fe5353;
}
:deep {
.van-checkbox__icon {
font-size: 16px;
}
}
}
.mb50 {
margin-bottom: 50px;
}
.mb75 {
margin-bottom: 75px;
}
.agreement {
display: flex;
align-items: center;
p {
font-size: 20px;
margin-left: 10px;
}
span {
color: #fe5353;
}
}
.submit {
display: block;
width: 100%;
line-height: 99px;
background-color: #fe5353;
border-radius: 50px;
border: 1px solid #fe5353;
font-weight: bold;
font-size: 32px;
color: #ffffff;
}
}
</style>

View File

@ -0,0 +1,64 @@
<script setup>
import {computed, ref} from "vue";
import IndexList from "@/components/index-list/index-list.vue";
import Tabbar from "@/components/tabbar/tabbar.vue";
import TabHeader from "@/components/head/head.vue";
import Tabs from "@/components/tabs/tabs.vue";
import NavBar from "@/components/nav-bar/nav-bar.vue";
import { useRoute } from "vue-router";
import Content from "@/components/content/content.vue";
import { useWorksStore } from "@/pinia/works.js";
import { storeToRefs } from "pinia";
const worksStore = useWorksStore();
const { data } = storeToRefs(worksStore);
const route = useRoute();
const pageType = route.query.pageType || "1";
const navList = [
{ name: "平台素材", id: "1" },
{ name: "达人分享", id: "2" },
];
const list = ref([]);
const current = ref(1);
const hotList = computed(() => {
return data.value.release;
});
const switchCurrent = (option) => {
current.value = option.ind;
list.value = hotList.value.filter((item) => item.classify === option.name);
};
window.initData = (data) => {
console.log(data, "获取参数");
};
switchCurrent({ ind: pageType - 1, name: pageType });
</script>
<template>
<div class="wrap">
<tab-header :sticky="true">
<nav-bar
title="素材库"
:left-arrow="!!route.query.pageType"
:border="false"
></nav-bar>
<tabs
:current="current"
:list-num="3"
switch-name="id"
:list="navList"
@on-switch="switchCurrent"
></tabs>
</tab-header>
<content :padding-bottom="98" :padding-top="110">
<div class="list-box">
<index-list :list="list"></index-list>
</div>
</content>
<tabbar select-tab="material"></tabbar>
</div>
</template>
<style scoped lang="scss">
.list-box {
padding: 0 26px 55px;
}
</style>

View File

@ -0,0 +1,173 @@
<script setup>
import { ref } from "vue";
import { pop } from "@/assets/js/useCustomRouter.js";
import { useRoute } from "vue-router";
import { showDialog, showToast } from "vant";
import { useWorksStore } from "@/pinia/works.js";
import { systemInfoStore } from "@/pinia/systemInfo.js";
import { storeToRefs } from "pinia";
const store = systemInfoStore();
const { systemInfo } = storeToRefs(store);
const worksStore = useWorksStore();
// import { useNativePreviewFile } from "@/assets/js/useNative.js";
const route = useRoute();
const details = route.query;
const show = ref(false);
const message = ref("");
const back = () => {
pop();
};
// const open = () => {
// useNativePreviewFile({
// url: "https://jpfz.qhdsafety.com/demo/%E9%99%84%E4%BB%B6.doc",
// });
// };
const onSubmit = () => {
if (!message.value) {
showToast("请输入举报信息");
return false;
}
message.value = "";
show.value = false;
showDialog({
title: "提示",
message: "举报成功我们将在7-15工作日处理",
confirmButtonText: "确认",
});
};
const shield = (type) => {
const msg = type === "author" ? "作者" : "作品";
showDialog({
title: "提示",
message: `是否屏蔽该${msg}`,
confirmButtonText: "确认屏蔽",
showCancelButton: true,
})
.then(() => {
worksStore.setShield(details, type);
back();
})
.catch((err) => {
console.log(err);
});
};
</script>
<template>
<div class="wrap">
<img :src="details.url" alt="" />
<div class="details-content">
<div class="title">
{{ details.name }}
</div>
<div class="back" :style="{ top: systemInfo.batteryBarsHeight + 'px' }">
<van-button class="ml-5" size="small" color="red" @click="show = true"
>举报</van-button
>
<van-button
v-if="details.classify === '2'"
class="ml-5"
size="small"
color=""
@click="shield('author')"
>屏蔽作者</van-button
>
<van-button class="ml-5" size="small" color="" @click="shield('works')"
>屏蔽作品</van-button
>
<div class="nav-style" @click="back">
<van-icon name="cross" color="#fff" size="20px" />
</div>
</div>
</div>
<van-popup v-model:show="show" round>
<div class="report">
<div class="title">举报</div>
<div class="report_input">
<van-field
v-model="message"
:border="true"
rows="1"
autosize
type="textarea"
placeholder="请输入"
/>
</div>
<div class="btn">
<van-button size="small" @click="show = false">取消</van-button>
<van-button size="small" color="#fe5353" @click="onSubmit"
>提交</van-button
>
</div>
</div>
</van-popup>
</div>
</template>
<style scoped lang="scss">
.wrap {
height: 100vh;
background-color: #000;
display: flex;
align-items: center;
justify-items: center;
> img {
width: 100%;
}
.details-content {
position: fixed;
padding: 20px 36px 100px;
width: 100%;
bottom: 0;
background-color: #000;
.title {
font-size: 26px;
line-height: 40px;
color: #fff;
}
.nav-style {
width: 80px;
height: 80px;
border-radius: 50%;
background-color: #262626;
display: flex;
align-items: center;
justify-content: center;
img {
width: 40px;
height: 40px;
}
}
}
.back {
position: fixed;
right: 60px;
display: flex;
align-items: center;
}
}
.report {
width: 600px;
padding: 45px;
border-radius: 10px;
.title {
text-align: center;
margin-bottom: 25px;
}
.report_input {
border: 1px solid #dedede;
}
.btn {
margin-top: 45px;
display: flex;
justify-content: space-between;
button {
width: calc(50% - 10px);
}
}
}
.ml-5 {
margin-right: 10px;
}
</style>

View File

@ -0,0 +1,52 @@
<script setup>
import NavBar from "@/components/nav-bar/nav-bar.vue";
import { storeToRefs } from "pinia";
import IndexList from "@/components/index-list/index-list.vue";
import { ref, computed } from "vue";
import Tabs from "@/components/tabs/tabs.vue";
import { useUserStore } from "@/pinia/user.js";
import TabHeader from "@/components/head/head.vue";
import Content from "@/components/content/content.vue";
const userStore = useUserStore();
const { data } = storeToRefs(userStore);
const navList = [
{ name: "审核中", id: 1 },
{ name: "审核通过", id: 2 },
{ name: "审核未通过", id: 3 },
];
const current = ref(0);
const switchCurrent = (option) => {
current.value = option.ind;
};
const list = computed(() => {
console.log(data.value, "data.value");
return data.value.release;
});
</script>
<template>
<div class="wrap">
<tab-header :sticky="true">
<nav-bar title="我的发布" :left-arrow="true" :border="false"></nav-bar>
<tabs
:current="current"
:list-num="3"
:list="navList"
switch-name="id"
@on-switch="switchCurrent"
></tabs>
</tab-header>
<content>
<div class="list-box">
<index-list :list="list"></index-list>
</div>
</content>
</div>
</template>
<style scoped lang="scss">
.list-box {
padding: 0 26px;
}
</style>

View File

@ -0,0 +1,228 @@
<script setup>
import { ref } from "vue";
import {
useNativeCamera,
useNativeChooseFile,
useNativeChooseMedia,
useNativeRecordVideo,
} from "@/assets/js/useNative.js";
import { showToast } from "vant";
import { pop } from "@/assets/js/useCustomRouter.js";
import { useUserStore } from "@/pinia/user.js";
import NavBar from "@/components/nav-bar/nav-bar.vue";
import TabHeader from "@/components/head/head.vue";
import Content from "@/components/content/content.vue";
const userStore = useUserStore();
const form = ref({
username: "",
type: "",
cover: [],
video: [],
file: "",
});
const formField = ref("");
const columns = ref([
{ text: "经典转场", value: 1 },
{ text: "唯美风景", value: 2 },
{ text: "最火高清", value: 3 },
]);
const uploadColumns = ref([
{ name: "拍摄", value: 1 },
{ name: "从相册选择", value: 2 },
]);
const uploadType = ref("");
const uploadPicker = ref(false);
const showPicker = ref(false);
const onConfirm = ({ selectedOptions }) => {
showPicker.value = false;
form.value.type = selectedOptions[0].text;
};
const uploadConfirm = (selectedValues) => {
const param = { type: [uploadType.value], count: 1, multiple: false };
uploadPicker.value = false;
if (selectedValues.value === 2) {
useNativeChooseMedia(param, (data) => {
const imgItem = { url: data[0].path };
form.value[formField.value].push(imgItem);
});
return;
}
if (uploadType.value === "image") {
useNativeCamera({}, (data) => {
const imgItem = { url: data.path };
form.value[formField.value].push(imgItem);
});
return;
}
useNativeRecordVideo({}, (data) => {
const videoItem = { url: data.path };
form.value[formField.value].push(videoItem);
});
};
const clickUpload = (field, type) => {
uploadType.value = type;
formField.value = field;
uploadPicker.value = true;
};
const onSubmit = () => {
form.value.url = form.value.cover[0].url;
form.value.hot = 0;
userStore.setUserData(form.value, "release");
showToast({
message: "保存成功",
duration: 2000,
});
setTimeout(() => {
pop();
}, 2000);
};
const upload = () => {
useNativeChooseFile({ type: "doc" }, (data) => {
console.log(data, "选择文件");
form.value.file = data.path;
});
};
</script>
<template>
<div class="wrap">
<tab-header>
<nav-bar title="我要上传" :left-arrow="true" :border="false"></nav-bar>
</tab-header>
<content>
<div class="form-box">
<van-form @submit="onSubmit" input-align="right">
<van-cell-group inset>
<div class="group-box">
<van-field
v-model="form.type"
is-link
readonly
name="picker"
label="分类"
placeholder="请选择分类"
:rules="[{ required: true, message: '请选择分类' }]"
@click="showPicker = true"
/>
</div>
<div class="group-box">
<van-field
v-model="form.username"
name="名称"
label="名称"
placeholder="请选择名称"
:rules="[{ required: true, message: '请输入名称' }]"
/>
</div>
<van-field
name="uploader"
label-align="top"
input-align="left"
label="上传封面"
:rules="[{ required: true, message: '请上传封面图' }]"
>
<template #input>
<div class="upload-box">
<van-uploader
@click-upload="clickUpload('cover', 'image')"
:readonly="true"
v-model="form.cover"
/>
</div>
</template>
</van-field>
<van-field
name="uploader"
label-align="top"
input-align="left"
label="上传视频"
:rules="[{ required: true, message: '请上传视频' }]"
>
<template #input>
<div class="upload-box">
<van-uploader
accept="video/*"
@click-upload="clickUpload('video', 'video')"
:readonly="true"
v-model="form.video"
/>
</div>
</template>
</van-field>
<van-field
name="uploader"
label-align="top"
input-align="left"
label="上传文件"
>
<template #input>
<van-button type="primary" @click="upload"></van-button>
</template>
</van-field>
<van-popup v-model:show="showPicker" position="bottom">
<van-picker
:columns="columns"
@confirm="onConfirm"
@cancel="showPicker = false"
/>
</van-popup>
<van-action-sheet
v-model:show="uploadPicker"
:actions="uploadColumns"
cancel-text="取消"
@select="uploadConfirm"
close-on-click-action
@cancel="uploadPicker = false"
/>
</van-cell-group>
<div class="button-box">
<van-button
round
block
color="#FC5B5B"
type="primary"
native-type="submit"
>
提交
</van-button>
</div>
</van-form>
</div>
</content>
</div>
</template>
<style scoped lang="scss">
.form-box {
padding-top: 30px;
}
.group-box {
padding: 15px 0;
position: relative;
&:after {
position: absolute;
box-sizing: border-box;
content: " ";
pointer-events: none;
right: var(--van-padding-md);
bottom: 0;
left: var(--van-padding-md);
border-bottom: 1px solid var(--van-cell-border-color);
transform: scaleY(0.5);
}
}
.button-box {
padding: 0 40px;
position: fixed;
bottom: 50px;
left: 0;
width: 100%;
}
.upload-box {
padding: 15px 0;
}
:deep(.van-uploader__file-name.van-ellipsis) {
display: none;
}
</style>

View File

@ -0,0 +1,60 @@
<script setup>
import { computed, ref } from "vue";
import IndexList from "@/components/index-list/index-list.vue";
import Tabs from "@/components/tabs/tabs.vue";
import NavBar from "@/components/nav-bar/nav-bar.vue";
import TabHeader from "@/components/head/head.vue";
import Content from "@/components/content/content.vue";
import { useWorksStore } from "@/pinia/works.js";
import { storeToRefs } from "pinia";
const worksStore = useWorksStore();
const { data } = storeToRefs(worksStore);
const navList = [
{ name: "都市风格", id: "1" },
{ name: "唯美风景", id: "2" },
{ name: "黑白照片", id: "3" },
];
const hotList = computed(() => {
return data.value.release.filter((item) => item.classify === "1");
});
window.pageShow = () => {
console.log("页面显示");
};
window.pageHide = () => {
console.log("页面隐藏");
};
const list = ref([]);
const current = ref(0);
const switchCurrent = (option) => {
current.value = option.ind;
list.value = hotList.value.filter((item) => item.type === option.name);
};
switchCurrent({ ind: 0, name: "1" });
</script>
<template>
<div class="wrap">
<tab-header :sticky="true">
<nav-bar title="平台作品" :left-arrow="true" :border="false"></nav-bar>
<tabs
:current="current"
:list-num="3"
switch-name="id"
:list="navList"
@on-switch="switchCurrent"
></tabs>
</tab-header>
<content :padding-top="110">
<div class="list-box">
<index-list :list="list"></index-list>
</div>
</content>
</div>
</template>
<style scoped lang="scss">
.list-box {
padding: 0 26px 55px;
}
</style>

View File

@ -0,0 +1,16 @@
<template>
<tab-header :sticky="true">
<nav-bar title="隐私协议" :left-arrow="true" :border="false"></nav-bar>
</tab-header>
<iframe
src="https://jpfz.qhdsafety.com/demo/privacy.html"
style="width: 100vw; height: calc(100vh - 48px); margin-top: 48px"
frameborder="0"
allowfullscreen="true"
/>
</template>
<script setup lang="ts">
import NavBar from "@/components/nav-bar/nav-bar.vue";
import TabHeader from "@/components/head/head.vue";
</script>

View File

@ -0,0 +1,262 @@
<script setup>
import { ref } from "vue";
import NavBar from "@/components/nav-bar/nav-bar.vue";
import { systemInfoStore } from "@/pinia/systemInfo.js";
import { storeToRefs } from "pinia";
import err1 from "@/assets/images/err-icon1.png";
import err2 from "@/assets/images/err-icon2.png";
import err3 from "@/assets/images/err-icon3.png";
import err4 from "@/assets/images/err-icon4.png";
import err5 from "@/assets/images/err-icon5.png";
import {
useNativeCamera,
useNativeChooseMedia,
useNativeRecordVideo,
} from "@/assets/js/useNative.js";
import { showToast } from "vant";
import { pop } from "@/assets/js/useCustomRouter.js";
const store = systemInfoStore();
const { systemInfo } = storeToRefs(store);
const form = ref({
username: "",
cover: [],
errType: undefined,
});
const errorList = [
{ id: "1", url: err1, name: "系统错误" },
{ id: "2", url: err2, name: "界面优化" },
{ id: "3", url: err3, name: "设计缺陷" },
{ id: "4", url: err4, name: "性能问题" },
{ id: "5", url: err5, name: "其他问题" },
];
const uploadColumns = ref([
{ name: "拍摄", value: 1 },
{ name: "从相册选择", value: 2 },
]);
const formField = ref("");
const uploadType = ref("");
const uploadPicker = ref(false);
const uploadConfirm = (selectedValues) => {
const param = { type: [uploadType.value], count: 1, multiple: false };
uploadPicker.value = false;
if (selectedValues.value === 2) {
useNativeChooseMedia(param, (data) => {
const imgItem = { url: data[0].path };
form.value[formField.value].push(imgItem);
});
return;
}
if (uploadType.value === "image") {
useNativeCamera({}, (data) => {
const imgItem = { url: data.path };
form.value[formField.value].push(imgItem);
});
return;
}
useNativeRecordVideo({}, (data) => {
const videoItem = { url: data.path };
form.value[formField.value].push(videoItem);
});
};
const clickUpload = (field, type) => {
uploadType.value = type;
formField.value = field;
uploadPicker.value = true;
};
const selectErrType = (id) => {
form.value.errType = id;
};
const onSubmit = () => {
showToast({
message: "保存成功",
duration: 2000,
});
setTimeout(() => {
pop();
}, 2000);
};
</script>
<template>
<div class="wrap">
<div
class="head"
:style="{ paddingTop: systemInfo.batteryBarsHeight + 'px' }"
>
<nav-bar title="问题反馈" :left-arrow="true" :border="false"></nav-bar>
</div>
<div class="content-box">
<van-form @submit="onSubmit" input-align="left">
<van-cell-group inset>
<div class="group-box">
<van-field
v-model="form.username"
name="标题"
label=""
placeholder="请输入标题"
:rules="[{ required: true, message: '请输入标题' }]"
/>
</div>
<div class="error-box">
<van-field
name="错误类型"
v-model="form.errType"
label=""
:rules="[{ required: true, message: '请选择错误类型' }]"
>
<template #input>
<div class="error-list">
<div
class="error-item"
:class="{ active: form.errType === item.id }"
v-for="item in errorList"
:key="item.id"
@click="selectErrType(item.id)"
>
<img :src="item.url" :alt="item.name" />
<span>{{ item.name }}</span>
</div>
</div>
</template>
</van-field>
</div>
<div class="content">
<span>我要反馈</span>
<div class="content-child">
<van-field
:border="false"
v-model="form.message"
rows="1"
autosize
label=""
type="textarea"
placeholder="你想说点什么"
/>
<van-field
name="uploader"
label-align="top"
input-align="left"
label=""
>
<template #input>
<div class="upload-box">
<van-uploader
:readonly="true"
v-model="form.cover"
@click-upload="clickUpload('cover', 'image')"
/>
</div>
</template>
</van-field>
</div>
</div>
<van-action-sheet
v-model:show="uploadPicker"
:actions="uploadColumns"
cancel-text="取消"
@select="uploadConfirm"
close-on-click-action
@cancel="uploadPicker = false"
/>
</van-cell-group>
<button class="submit">确定</button>
</van-form>
</div>
</div>
</template>
<style scoped lang="scss">
.head {
background-color: #1082ff;
:deep(.van-nav-bar) {
background-color: #1082ff;
.van-nav-bar__title {
color: #fff;
}
.van-icon {
color: #fff;
}
}
}
.content-box {
margin-top: 26px;
.group-box {
position: relative;
&:after {
position: absolute;
box-sizing: border-box;
content: " ";
pointer-events: none;
right: var(--van-padding-md);
bottom: 0;
left: var(--van-padding-md);
border-bottom: 1px solid var(--van-cell-border-color);
transform: scaleY(0.5);
}
}
}
.error-box {
padding-top: 15px;
}
.error-list {
display: flex;
flex-wrap: wrap;
.error-item {
width: calc((100% - 64px) / 3);
padding: 33px 0 26px;
margin-right: 32px;
margin-bottom: 32px;
background: #f6f7fb;
border-radius: 7px;
&:nth-child(3n) {
margin-right: 0;
}
img {
width: 40px;
display: block;
margin: 0 auto;
}
span {
display: block;
text-align: center;
font-weight: 400;
font-size: 27px;
color: #9fa7bc;
margin-top: 20px;
}
}
.active {
background-color: #1082ff;
}
}
.content {
padding: 0 32px;
> span {
font-weight: bold;
display: block;
margin-bottom: 40px;
}
.content-child {
background-color: #f8f9fd;
}
:deep(.van-cell) {
background-color: rgba(0, 0, 0, 0);
.van-uploader__upload {
border: 4px dashed #c1c4cd;
border-radius: 17px;
}
}
}
.submit {
display: block;
width: calc(100% - 64px);
line-height: 99px;
background-color: #1082ff;
border-radius: 7px;
border: 1px solid #1082ff;
font-weight: bold;
font-size: 32px;
color: #ffffff;
margin: 64px auto 0;
}
</style>

View File

@ -0,0 +1,133 @@
<script setup>
import { ref } from "vue";
import NavBar from "@/components/nav-bar/nav-bar.vue";
import { redirect } from "@/assets/js/useCustomRouter.js";
import TabHeader from "@/components/head/head.vue";
import Content from "@/components/content/content.vue";
const historyList = ref([]);
const hotList = ref([
{ name: "都市", id: "1" },
{ name: "风景", id: 2 },
{ name: "雪", id: 2 },
]);
const searchValue = ref("");
const onSearch = () => {
console.log("重定向跳转");
redirect({
url: "/search/search_list",
params: {
value: searchValue.value,
},
});
};
const selectSearch = (value) => {
searchValue.value = value;
onSearch();
};
</script>
<template>
<div class="wrap">
<tab-header>
<nav-bar title="搜索" :left-arrow="true" :border="false"></nav-bar>
</tab-header>
<content :padding-top="30">
<div class="search-box">
<div class="search">
<input
type="text"
v-model="searchValue"
maxlength="8"
placeholder="搜索您需要的素材"
/>
<van-icon name="search" size="20px" />
</div>
<span @click="onSearch"></span>
</div>
<div class="search-nav">
<span class="title">历史搜索</span>
<div class="search-nav-box">
<span
v-for="item in historyList"
:key="item.name"
@click="selectSearch(item.name)"
>{{ item.name }}</span
>
</div>
</div>
<div class="search-nav">
<span class="title">推荐搜索</span>
<div class="search-nav-box">
<span
v-for="item in hotList"
:key="item.name"
@click="selectSearch(item.name)"
>{{ item.name }}</span
>
</div>
</div>
</content>
</div>
</template>
<style scoped lang="scss">
.search-box {
margin-top: 40px;
display: flex;
align-items: center;
padding-left: 25px;
padding-bottom: 30px;
> span {
font-size: 28px;
color: #ff420c;
padding: 10px 25px;
}
}
.search {
flex: 1;
height: 88px;
background-color: #f6f7f8;
border-radius: 44px;
border: 1px solid #ececec;
display: flex;
align-items: center;
padding: 0 36px 0 30px;
input {
border: 1px solid rgba(0, 0, 0, 0);
height: 100%;
background-color: #f6f7f8;
flex: 1;
font-weight: 500;
font-size: 27px;
}
}
.search-nav {
margin-top: 60px;
padding: 0 25px;
.title {
display: block;
font-weight: bold;
font-size: 35px;
color: #222222;
margin-bottom: 38px;
}
.search-nav-box {
display: flex;
flex-wrap: wrap;
span {
min-width: 140px;
padding: 0 26px;
margin-right: 30px;
margin-bottom: 30px;
background-color: #f6f7f8;
border-radius: 24px;
line-height: 48px;
font-weight: 500;
font-size: 24px;
text-align: center;
color: #444444;
}
}
}
</style>

View File

@ -0,0 +1,226 @@
<script setup>
import { computed, ref } from "vue";
import { pop, push } from "@/assets/js/useCustomRouter.js";
import { useRoute } from "vue-router";
import IndexList from "@/components/index-list/index-list.vue";
import TabHeader from "@/components/head/head.vue";
import Content from "@/components/content/content.vue";
import { useWorksStore } from "@/pinia/works.js";
import { storeToRefs } from "pinia";
const worksStore = useWorksStore();
const { data } = storeToRefs(worksStore);
const route = useRoute();
const details = route.query;
window.initData = (data) => {
console.log(data, "获取参数");
};
const searchValue = ref(details.value);
const current = ref(0);
const tabList = ref([
{ name: "素材", type: 1 },
{ name: "文案", type: 2 },
]);
const searchList = ref([]);
const allMaterial = computed(() => {
return data.value.release;
});
const list = ref([
{
url: "https://fastly.picsum.photos/id/799/300/400.jpg?hmac=skUMPxX6ZcZmkbpXkyOV7flzeglSNvp1fNcEvoU6e1c",
name: "你的生活再苦,别人也没法替你承担。想要的东西,只能自己去赚去拼。",
},
{
url: "https://fastly.picsum.photos/id/518/400/300.jpg?hmac=SBiwfW8E5aDfYKLvB6-s-pp7SA7WntrI8GjPkpzhd_k",
name: "前方的路可能充满崎岖唯有奋斗才能不惧风雨。",
},
{
url: "https://fastly.picsum.photos/id/65/300/400.jpg?hmac=lhM904DByZf8WL8u56mtPZO9TA_ipwYDTOoatdjPzx4",
name: "不要瞻前顾后,拼尽全力向前奔跑吧。",
},
{
url: "https://fastly.picsum.photos/id/239/300/300.jpg?hmac=esyLH7XPNWHLXWLu_Dvs59SpxUEnrBHF4FV_02YOak4",
name: "真正的努力大多是默默的、不起眼的、不叫嚣的,人大多是不知不觉。",
},
{
url: "https://fastly.picsum.photos/id/317/300/300.jpg?hmac=gg_enWI-dwSZM7YuLo3LA-dVghfkTvugM7iukp_4RR0",
name: "以风筝之线为始,以彩虹之海为终。",
},
{
url: "https://fastly.picsum.photos/id/408/300/400.jpg?hmac=1pTfKCG8_tiCVSoQSOYZ8zlKnh2jBmhZWpf3zy9X0hc",
name: "老人说,今天他来晚了,钓的是一篓暮色,明天他要早起,一定能钓到一江朝阳。",
},
{
url: "https://fastly.picsum.photos/id/866/200/300.jpg?hmac=rcadCENKh4rD6MAp6V_ma-AyWv641M4iiOpe1RyFHeI",
name: "我曾看到一个时间旅人,从身上拍落两场大雪,由心里携出一篮火焰,独自穿过整个冬天。",
},
{
url: "https://fastly.picsum.photos/id/417/400/300.jpg?hmac=AeJwv7oRTFt1ifIPEbJFXw1B0qYJVeauRbaOXUhnmAY",
name: "所谓压力,其实是自身的能力不足;所谓困难,其实是自己的本事不够",
},
]);
const onSearch = (ind) => {
if (ind === 1) {
searchList.value = allMaterial.value.filter((item) => {
return item.name.indexOf(searchValue.value) !== -1;
});
return;
}
console.log(searchList.value);
searchList.value = list.value.filter((item) => {
return item.name.indexOf(searchValue.value) !== -1;
});
console.log(searchList.value);
};
const switchType = (ind) => {
current.value = ind;
onSearch(ind);
};
const viewDetails = (item) => {
push({
url: "/material/material_details",
params: item,
});
};
const back = () => {
pop();
};
switchType(1);
</script>
<template>
<div class="wrap">
<tab-header :sticky="true">
<div>
<div class="header">
<van-icon
name="arrow-left"
color="#3C3B3B"
size="20px"
@click="back"
/>
<div class="search-box">
<div class="search">
<input
type="text"
v-model="searchValue"
maxlength="8"
placeholder="搜索您需要的素材"
/>
<van-icon name="search" size="20px" />
</div>
<span @click="onSearch(current)"></span>
</div>
</div>
<div class="tabs">
<span
v-for="item in tabList"
:key="item.name"
:class="{ active: current === item.type }"
@click="switchType(item.type)"
>{{ item.name }}</span
>
</div>
</div>
</tab-header>
<content :padding-top="70">
<div class="list-box" v-if="current === 1">
<index-list :list="searchList"></index-list>
</div>
<div class="content-list" v-if="current === 2">
<div
class="content-list-item"
v-for="item in searchList"
:key="item.name"
@click="viewDetails(item)"
>
<img :src="item.url" alt="" />
<span>{{ item.name }}</span>
</div>
</div>
</content>
</div>
</template>
<style scoped lang="scss">
.header {
display: flex;
align-items: center;
padding: 0 50px 20px 26px;
background-color: #fff;
}
.search-box {
display: flex;
align-items: center;
padding-left: 25px;
> span {
font-size: 28px;
color: #ff420c;
padding: 10px 25px;
}
}
.search {
flex: 1;
height: 88px;
margin-left: 15px;
background-color: #f6f7f8;
border-radius: 44px;
border: 1px solid #ececec;
display: flex;
align-items: center;
padding: 0 36px 0 30px;
input {
border: 1px solid rgba(0, 0, 0, 0);
height: 100%;
background-color: #f6f7f8;
flex: 1;
font-weight: 500;
font-size: 27px;
}
}
.tabs {
display: flex;
align-items: center;
padding: 0 26px 10px 26px;
background-color: #fff;
span {
padding: 0 25px;
color: #999999;
font-size: 26px;
}
.active {
font-size: 32px;
color: #000;
font-weight: bold;
}
}
.content-list {
-moz-column-count: 2; /* Firefox */
-webkit-column-count: 2; /* Safari 和 Chrome */
column-count: 2;
column-gap: 16px;
padding: 26px;
.content-list-item {
margin-bottom: 20px;
break-inside: avoid;
img {
width: 100%;
display: block;
border-radius: 10px;
}
span {
display: block;
margin-top: 15px;
font-size: 30px;
line-height: 35px;
font-weight: bold;
color: #3a3a3c;
}
}
}
.list-box {
padding: 0 26px 55px;
}
</style>

View File

@ -0,0 +1,283 @@
<script setup>
import { ref } from "vue";
import { storeToRefs } from "pinia";
import Tabbar from "@/components/tabbar/tabbar.vue";
import { push, reLaunch } from "@/assets/js/useCustomRouter.js";
import {
getUpdateVersion,
// useNativeActivateCamera,
// useNativeActivateCameraClose,
// useNativeActivateCameraSetMessage,
// useNativeGetLocation,
// useNativeGetPermission,
// useNativePushClientId,
// useNativeSystemInfo,
// useNativePreviewFile,
// useNativeGetNetworkType,
// useNativeGetVersion,
// useNativeOpenBrowser,
// useNativeQuitApp,
} from "@/assets/js/useNative.js";
import { useUserStore } from "@/pinia/user.js";
import Content from "@/components/content/content.vue";
import { systemInfoStore } from "@/pinia/systemInfo.js";
const store = systemInfoStore();
const { systemInfo } = storeToRefs(store);
const userStore = useUserStore();
const { data } = storeToRefs(userStore);
const navLst = ref([
{
name: "我的发布",
fun: () => {
push({ url: "/user/my_release", params: {} });
},
},
{
name: "修改密码",
fun: () => {
push({ url: "/change_password", params: {} });
},
},
{
name: "版本更新",
fun: () => {
console.log("版本更新");
getUpdateVersion();
},
},
{
name: "意见反馈",
fun: () => {
push({ url: "/user/problem_feedback", params: {} });
},
},
{
name: "隐私协议",
fun: () => {
push({ url: "/user/privacy_agreement", params: {} });
},
},
{
name: "用户协议",
fun: () => {
push({ url: "/user/user_agreement", params: {} });
},
},
{
name: "注销",
fun: () => {
push({ url: "/cancel_account", params: {} });
},
},
// {
// name: "",
// fun: () => {
// useNativeGetLocation({}, (data) => {
// console.log(data, "");
// });
// },
// },
// {
// name: "",
// fun: () => {
// useNativeSystemInfo({}, (data) => {
// console.log(data, "");
// });
// },
// },
// {
// name: "CID",
// fun: () => {
// useNativePushClientId({}, (data) => {
// console.log(data, "CID");
// });
// },
// },
// {
// name: "",
// fun: () => {
// useNativeGetPermission(
// { type: "location", title: "", describe: "" },
// (data) => {
// console.log(data, "");
// },
// );
// },
// },
// {
// name: "app",
// fun: () => {
// useNativePreviewFile({
// url: "https://jpfz.qhdsafety.com/demo/%E9%99%84%E4%BB%B6.doc",
// });
// },
// },
// {
// name: "",
// fun: () => {
// useNativeGetNetworkType({}, (data) => {
// console.log(data, "");
// });
// },
// },
// {
// name: "",
// fun: () => {
// useNativeGetVersion({}, (data) => {
// console.log(data, "");
// });
// },
// },
// {
// name: "",
// fun: () => {
// useNativeOpenBrowser({ url: "https://www.baidu.com/" });
// },
// },
// {
// name: "退app",
// fun: () => {
// useNativeQuitApp({});
// },
// },
]);
window.pageShow = () => {
console.log("页面显示");
};
window.pageHide = () => {
console.log("页面隐藏");
};
// const replace = () => {
// const num = 0;
// useNativeActivateCamera({ interval: 2000 }, (data) => {
// console.log(data, "");
// });
// useNativeActivateCameraSetMessage({ message: "" });
// numNext(num);
// };
// const numNext = (num) => {
// console.log("num", num);
// useNativeActivateCameraSetMessage({ message: "" + num });
// if (num < 10) {
// num++;
// setTimeout(() => {
// numNext(num);
// }, 1000);
// } else {
// console.log("");
// useNativeActivateCameraClose();
// }
// };
const logout = () => {
// useNativeQuitApp();
reLaunch({
url: "/login",
params: {},
});
};
</script>
<template>
<div class="wrap">
<div>
<div
class="user-head"
:style="{
paddingTop: systemInfo.batteryBarsHeight + 'px',
}"
>
<span>个人中心</span>
</div>
</div>
<content :padding-bottom="162">
<div class="header">
<div class="user-box">
<img src="@/assets/images/user_pic.png" alt="" />
<span>用户{{ data.userInfo.userName }}</span>
</div>
</div>
<div class="nav-list">
<div
class="nav-list-item"
v-for="(item, index) in navLst"
:key="index"
@click="item.fun"
>
<span>{{ item.name }}</span>
<van-icon name="arrow" color="#999999" size="14px" />
</div>
</div>
<button class="logout" @click="logout">退</button>
</content>
<tabbar select-tab="user"></tabbar>
</div>
</template>
<style scoped lang="scss">
.wrap {
background-color: #f9f9f9;
min-height: 100vh;
padding-bottom: 126px;
}
.header {
padding: 0 0 50px;
flex-direction: column;
background-image: url("../../assets/images/login_bj.jpg");
background-size: cover;
background-position: top center;
.user-box {
display: flex;
align-items: center;
padding: 0 35px;
img {
width: 120px;
background-color: #fff;
border-radius: 50%;
border: 2px solid #fff;
margin-right: 25px;
}
span {
font-weight: bold;
font-size: 29px;
color: #222222;
}
}
}
:deep(.van-nav-bar) {
background-color: rgba(0, 0, 0, 0);
}
.user-head {
//padding-top: 100px;
text-align: center;
font-size: 24px;
}
.nav-list {
margin: 20px 31px 50px;
border-radius: 13px;
background-color: #fff;
padding: 37px 34px;
.nav-list-item {
display: flex;
justify-content: space-between;
align-items: center;
span {
font-weight: bold;
font-size: 29px;
color: #222222;
line-height: 100px;
}
}
}
.logout {
display: block;
margin: 0 31px;
line-height: 100px;
background: #f0eeee;
border-radius: 13px;
width: calc(100% - 62px);
border: 1px solid #f0eeee;
font-weight: bold;
font-size: 29px;
color: rgba(102, 102, 102, 0.54);
}
</style>

View File

@ -0,0 +1,16 @@
<template>
<tab-header :sticky="true">
<nav-bar title="用户协议" :left-arrow="true" :border="false"></nav-bar>
</tab-header>
<iframe
src="https://jpfz.qhdsafety.com/demo/users_agreement.html"
style="width: 100vw; height: calc(100vh - 48px); margin-top: 48px"
frameborder="0"
allowfullscreen="true"
/>
</template>
<script setup lang="ts">
import NavBar from "@/components/nav-bar/nav-bar.vue";
import TabHeader from "@/components/head/head.vue";
</script>

83
demo/vite.config.js Normal file
View File

@ -0,0 +1,83 @@
import { defineConfig, loadEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import eslintPlugin from "vite-plugin-eslint";
import Components from "unplugin-vue-components/vite";
import { VantResolver } from "unplugin-vue-components/resolvers";
import EnhanceLog from "vite-plugin-enhance-log";
// import removeConsole from "vite-plugin-remove-console";
export default ({ mode }) => {
return defineConfig({
base: loadEnv(mode, process.cwd()).VITE_BASE,
plugins: [
vue(),
eslintPlugin(),
Components({
resolvers: [VantResolver()],
}),
// removeConsole({
// includes: [
// "assert",
// "clear",
// "count",
// "countReset",
// "createTask",
// "debug",
// "dir",
// "dirxml",
// "error",
// "group",
// "groupCollapsed",
// "groupEnd",
// "info",
// "log",
// "profile",
// "profileEnd",
// "table",
// "time",
// "timeEnd",
// "timeLog",
// "timeStamp",
// "trace",
// "warn",
// ],
// }),
EnhanceLog({
preTip: "🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀",
}),
],
server: {
host: true, // 本机的局域网IP不然其他人无法通过IP访问到0.0.0.0或true会自动获取本机的IP
port: 8099, // 端口号
open: true, // 是否自动打开浏览器
proxy: {
[loadEnv(mode, process.cwd()).VITE_PROXY]: {
target: loadEnv(mode, process.cwd()).VITE_BASE_URL,
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
resolve: {
alias: {
"@": "/src", // 别名,@代表src目录
},
extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"], // 引入文件时哪些后缀名可以不写
},
build: {
rollupOptions: {
// 打包多个入口文件
// input: {
// admin: path.resolve(__dirname, "index.html"),
// 其它入口文件路径需要为src/views/*/index.html
// },
output: {
chunkFileNames: "static/js/[name]-[hash].js",
entryFileNames: "static/js/[name]-[hash].js",
assetFileNames: "static/[ext]/name-[hash].[ext]",
},
},
},
});
};

4
demo1/.env Normal file
View File

@ -0,0 +1,4 @@
VITE_BASE_URL=1
VITE_PROXY=/api
VITE_FILE_PATH=1

1
demo1/.env.development Normal file
View File

@ -0,0 +1 @@
VITE_BASE=/

1
demo1/.env.production Normal file
View File

@ -0,0 +1 @@
VITE_BASE=/demo

4
demo1/.eslintignore Normal file
View File

@ -0,0 +1,4 @@
public
dist
package.json
!.prettierrc.cjs

62
demo1/.eslintrc.cjs Normal file
View File

@ -0,0 +1,62 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
"plugin:vue/vue3-essential",
"standard",
"@vue/prettier",
"eslint:recommended",
],
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
plugins: ["vue"],
rules: {
"no-console": "warn",
"vue/multi-word-component-names": "off",
"vue/no-v-html": "off",
"vue/require-default-prop": "off",
"camelcase": "off",
"eqeqeq": "error",
"vue/no-template-shadow": "error",
"vue/attribute-hyphenation": "error",
"vue/html-end-tags": "error",
"vue/eqeqeq": "error",
"vue/component-name-in-template-casing": ["error", "kebab-case"],
"vue/enforce-style-attribute": [
"error",
{ "allow": ["scoped", "module"] }
],
"vue/v-on-event-hyphenation": ["error", "always",{
"autofix": true,
}],
"vue/require-explicit-emits": "error",
"no-restricted-syntax": [
"error",
{
"selector": "VariableDeclarator[id.name='pd']",
"message": "不允许使用 pd请改用有语义化的变量名"
},
{
"selector": "ObjectExpression > Property[key.name='pd']",
"message": "不允许使用 pd请改用有语义化的变量名"
},
],
"no-unused-vars": [
"error",
{ vars: "all", args: "after-used", ignoreRestSiblings: false }
],
"linebreak-style": ["off",'windows'],
"no-restricted-properties": [
"error",
{ "object": "Object", "property": "assign"}
],
},
globals: {
defineOptions: "readonly",
},
};

24
demo1/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

4
demo1/.prettierrc.cjs Normal file
View File

@ -0,0 +1,4 @@
module.exports = {
extends: ["@vue/prettier", "plugin:prettier/recommended"],
endOfLine: "crlf",
};

7
demo1/README.md Normal file
View File

@ -0,0 +1,7 @@
# Vue 3 + Vite
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).

16
demo1/index.html Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />
<title>demo</title>
</head>
<body>
<div id="app"></div>
<noscript>
<strong>很抱歉如果没有启用JavaScript网站无法正常工作请启用JavaScript使其正常工作。</strong>
</noscript>
<script type="module" src="/src/main.js"></script>
</body>
</html>

10
demo1/jsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
]
}
}
}

10431
demo1/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

54
demo1/package.json Normal file
View File

@ -0,0 +1,54 @@
{
"name": "demo",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint --ext .js,.vue --fix src .prettierrc.cjs"
},
"dependencies": {
"@vant/weapp": "^1.11.7",
"@vueuse/core": "^10.11.1",
"animate.css": "^4.1.1",
"axios": "^1.7.4",
"dayjs": "^1.11.12",
"lodash-es": "^4.17.21",
"mitt": "^3.0.1",
"normalize.css": "^8.0.1",
"pinia": "^2.2.1",
"pinia-plugin-persistedstate": "^3.2.1",
"qs": "^6.13.0",
"throttle-debounce": "^5.0.2",
"vant": "^4.9.4",
"vue": "^3.4.37",
"vue-router": "^4.4.3"
},
"devDependencies": {
"@types/node": "^22.3.0",
"@vitejs/plugin-vue": "^5.1.2",
"@vue/eslint-config-prettier": "^9.0.0",
"autoprefixer": "^10.4.20",
"cnjm-postcss-px-to-viewport": "^1.0.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-n": "^16.6.2",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-vue": "^9.27.0",
"prettier": "^3.3.3",
"sass": "^1.77.8",
"unplugin-auto-import": "^0.18.2",
"unplugin-vue-components": "^0.27.4",
"vconsole": "^3.15.1",
"vite": "^5.4.0",
"vite-plugin-checker": "^0.7.2",
"vite-plugin-enhance-log": "^0.6.2",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-remove-console": "^2.2.0",
"vue-eslint-parser": "^9.4.3"
}
}

30
demo1/postcss.config.cjs Normal file
View File

@ -0,0 +1,30 @@
const path = require('path')
module.exports = {
plugins: {
autoprefixer: {
overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 8', '> 1%'],
grid: true,
},
"cnjm-postcss-px-to-viewport": {
unitToConvert: "px", // 要转化的单位
viewportWidth: 750, // UI设计稿的宽度
unitPrecision: 6, // 转换后的精度,即小数点位数
propList: ["*"], // 指定转换的css属性的单位*代表全部css属性的单位都进行转换
viewportUnit: "vw", // 指定需要转换成的视窗单位默认vw
fontViewportUnit: "vw", // 指定字体需要转换成的视窗单位默认vw
selectorBlackList: [".ignore", ".hairlines"],
minPixelValue: 1, // 默认值1小于或等于1px则不进行转换
mediaQuery: true, // 是否在媒体查询的css代码中也进行转换默认false
replace: true, // 是否转换后直接更换属性值
exclude: [], // 设置忽略文件,用正则做目录名匹配
landscape: false, // 是否处理横屏情况
// 如果没有使用其他的尺寸来设计下面这个方法可以不需要比如vant是375的
customFun: ({ file }) => {
// 这个自定义的方法是针对处理vant组件下的设计稿为375问题
return path.join(file).includes(path.join("node_modules", "vant"))
? 375
: 750;
},
},
},
};

1
demo1/public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

68
demo1/src/App.vue Normal file
View File

@ -0,0 +1,68 @@
<template>
<suspense>
<template #default>
<div>
<van-overlay :show="miscellaneousStore.getLoading">
<van-loading color="#fff" size="24px" vertical>加载中...</van-loading>
</van-overlay>
<router-view />
</div>
</template>
<template #fallback>
<div>加载中...</div>
</template>
</suspense>
</template>
<script setup>
import { useRoute } from "vue-router";
import { watchEffect } from "vue";
import { useMiscellaneousStore } from "@/pinia/miscellaneous.js";
import { systemInfoStore } from "@/pinia/systemInfo.js";
import { useNativeGetStatusBarHeight } from "@/assets/js/useNative.js";
const store = systemInfoStore();
const miscellaneousStore = useMiscellaneousStore();
const route = useRoute();
watchEffect(() => {
window.document.title = route.meta.title;
});
window.appShow = () => {
console.log("app显示了");
};
window.appHide = () => {
console.log("app隐藏了");
};
const getBarHeight = () => {
useNativeGetStatusBarHeight({}, (data) => {
console.log(data, "电池栏高度");
store.setSystemInfo(data.height, "batteryBarsHeight");
if (data.bottomSafeHeight) {
store.setSystemInfo(data.bottomSafeHeight, "bottomSafeHeight");
}
});
};
getBarHeight();
</script>
<style scoped lang="scss">
:root {
--van-uploader-size: 146px !important;
--van-button-mini-padding: 0 20px !important;
--van-nav-bar-z-index: 9 !important;
--van-nav-bar-background: rgb(51, 119, 255) !important;
--van-nav-bar-title-text-color: var(--van-white) !important;
--van-nav-bar-icon-color: var(--van-white) !important;
--van-nav-bar-text-color: var(--van-white) !important;
}
#app {
background-color: #fafafa;
min-height: 100vh;
font-size: 30px;
}
.van-loading {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>

View File

@ -0,0 +1,26 @@
* {
box-sizing: border-box;
}
body {
overflow: hidden;
-webkit-overflow-scrolling: touch;
}
// 5
// 使1使flexmin-width: 0;
@for $i from 1 through 5 {
.line-#{$i} {
@if $i == 1 {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
} @else {
display: -webkit-box !important;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
-webkit-line-clamp: $i;
-webkit-box-orient: vertical !important;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

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