feat: Init

This commit is contained in:
陶林 2024-12-16 20:56:14 +08:00
commit 8cc0770753
15 changed files with 10378 additions and 0 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL=https://alicdn.taoya.art/test/gradle-7.1.1-all.zip

28
.gitignore vendored Normal file
View File

@ -0,0 +1,28 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
.DS_Store
# Generated by package manager
node_modules/
# Generated by Cordova
/plugins/
/platforms/
dist/
logs/

1
.java-version Normal file
View File

@ -0,0 +1 @@
zulu64-17.0.9

73
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,73 @@
{
"typescript.enablePromptUseWorkspaceTsdk": true,
// Enable the flat config support
"eslint.experimental.useFlatConfig": true,
// Disable the default formatter
"prettier.enable": false,
"editor.formatOnSave": false,
"eslint.alwaysShowStatus": true,
// Auto fix
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
// Silent the stylistic rules in you IDE, but still auto fix them
"eslint.rules.customizations": [
{
"rule": "@stylistic/*",
"severity": "off"
},
{
"rule": "*-indent",
"severity": "off"
},
{
"rule": "*-spacing",
"severity": "off"
},
{
"rule": "*-spaces",
"severity": "off"
},
{
"rule": "*-order",
"severity": "off"
},
{
"rule": "*-dangle",
"severity": "off"
},
{
"rule": "*-newline",
"severity": "off"
},
{
"rule": "*quotes",
"severity": "off"
},
{
"rule": "*semi",
"severity": "off"
}
],
// The following is optional.
// It's better to put under project setting `.vscode/settings.json`
// to avoid conflicts with working with different eslint configs
// that does not support all formats.
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"yaml"
],
"i18n-ally.localesPaths": [
"layers/site/components/site/login/locale"
],
"typescript.tsdk": "node_modules/typescript/lib"
}

8
config.json Normal file
View File

@ -0,0 +1,8 @@
{
"homeUrl": "https://www.baidu.com",
"appId": "asd",
"appName": "asd",
"appVersion": "1.0.0",
"packType": "debug",
"pushEmail": "asd@qq.com"
}

28
config.xml Normal file
View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<widget xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" id="com.example.tao" version="1.0.0">
<name>APP</name>
<description>app</description>
<author email="dev@cordova.apache.org" href="https://cordova.apache.org"></author>
<content src="http://test.taoya.art/" />
<allow-intent href="http://*/*"/>
<allow-intent href="https://*/*"/>
<allow-intent href="tel:*"/>
<allow-intent href="sms:*"/>
<allow-intent href="mailto:*"/>
<allow-intent href="geo:*"/>
<allow-intent href="market:*"/>
<platform name="android">
<preference name="Fullscreen" value="true"/>
<icon src="logo.png"></icon>
</platform>
<!-- 设置Java和Gradle版本 -->
<preference name="android-minSdkVersion" value="24" />
<preference name="android-targetSdkVersion" value="34" />
<preference name="android-compileSdkVersion" value="34" />
<preference name="GradleVersion" value="7.6.3" />
<preference name="AndroidGradlePluginVersion" value="7.2.0" />
<preference name="JavaVersion" value="17" />
</widget>

35
eslint.config.js Normal file
View File

@ -0,0 +1,35 @@
import antfu from '@antfu/eslint-config'
export default antfu({
typescript: true,
stylistic: {
semi: false,
singleQuote: true,
indent: 2,
jsx: true,
},
rules: {
'vue/custom-event-name-casing': [2, 'kebab-case'], // 对自定义事件名称强制使用特定大小写
'style/max-statements-per-line': 'off',
// 允许单行if不换行
'antfu/if-newline': 'off',
// 仅单行if允许不使用大括号
'curly': ['error', 'multi-line'],
'no-console': 'off',
'node/prefer-global/process': 'off',
'ts/ban-types': 'off',
'ts/method-signature-style': 'off',
'unused-imports/no-unused-vars': 'off',
'array-callback-return': 'off',
'ts/ban-ts-comment': 'off',
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'ts/consistent-type-imports': 'off',
'ts/no-require-imports': 'off',
'ts/no-var-requires': 'off',
},
})

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

9612
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

45
package.json Normal file
View File

@ -0,0 +1,45 @@
{
"name": "io.cordova.hellocordova",
"displayName": "cordova",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"ad": "cordova platform rm android && cordova platform add android && cordova build android",
"clean": "rm -rf node_modules && rm -rf plugins && rm -rf platforms",
"initial": "tsx src/init.ts",
"build": "cordova build android",
"dev": "node scripts/dev.js",
"logo": "tsx scripts/logo.ts",
"allInOne": "npm run clean && npm i && npm run ad"
},
"keywords": [],
"author": "taolin taolin@taoya.art",
"license": "Apache-2.0",
"devDependencies": {
"@clack/prompts": "^0.8.2",
"@types/node": "^22.10.2",
"axios": "^1.7.9",
"chalk": "^5.3.0",
"consola": "^3.2.3",
"cordova": "^12.0.0",
"cordova-android": "^13.0.0",
"es6-promise-plugin": "^4.2.2",
"eslint": "^9.17.0",
"picocolors": "^1.1.1",
"tsx": "^4.19.2",
"typeorm": "^0.3.20",
"zod": "^3.24.1"
},
"cordova": {
"platforms": [
"browser",
"android"
],
"plugins": {}
},
"dependencies": {
"commander": "^12.1.0"
}
}

View File

140
scripts/cmd.ts Normal file
View File

@ -0,0 +1,140 @@
import { z } from 'zod';
import fs from 'fs/promises';
import path from 'path';
import { intro, outro, text, select, isCancel, cancel, group } from '@clack/prompts';
import { bgRed } from 'picocolors';
const ConfigSchema = z.object({
homeUrl: z.string().url({
message: "URL格式不正确"
}),
appId: z.string().min(1, {
message: "应用ID不能为空"
}).regex(/^[a-zA-Z0-9._-]+$/, {
message: "应用ID只能包含字母、数字、点、下划线和横线"
}),
appName: z.string().min(1, {
message: "应用名称不能为空"
}),
appVersion: z.string().regex(/^\d+\.\d+\.\d+$/, {
message: "版本号格式必须为 x.x.x"
}),
packType: z.enum(['release', 'debug'], {
errorMap: () => ({ message: "打包类型必须是 release 或 debug" })
}),
pushEmail: z.string().email({
message: "邮箱格式不正确"
})
});
type Config = z.infer<typeof ConfigSchema>;
async function main() {
intro('欢迎使用Cali配置工具');
const config = await group(
{
homeUrl: () => text({
message: '请输入主页URL',
validate(value) {
try {
new URL(value);
} catch {
return '请输入正确的网址';
}
},
}),
appId: () => text({
message: '请输入应用ID',
validate(value) {
if (!value.length) return '应用ID不能为空';
if (!/^[a-zA-Z0-9._-]+$/.test(value)) {
return '应用ID只能包含字母、数字、点、下划线和横线';
}
},
}),
appName: () => text({
message: '请输入应用名称',
validate(value) {
if (!value.length) return '应用名称不能为空';
},
}),
appVersion: () => text({
message: '请输入应用版本号',
validate(value) {
if (!/^\d+\.\d+\.\d+$/.test(value)) {
return '请输入正确的版本号,例如: 1.0.0';
}
},
}),
packType: () => select({
message: '请选择打包类型',
options: [
{ value: 'release', label: 'release' },
{ value: 'debug', label: 'debug' },
],
}),
pushEmail: () => text({
message: '请输入推送邮箱',
validate(value) {
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
return '请输入正确的邮箱地址';
}
},
}),
},
{
onCancel: () => {
cancel('操作已取消');
process.exit(0);
},
}
);
// 检查是否有任何输入被取消
for (const [key, value] of Object.entries(config)) {
if (isCancel(value)) {
cancel('操作已取消');
process.exit(0);
}
}
try {
// 使用zod验证配置
const result = ConfigSchema.safeParse(config);
if (!result.success) {
cancel('配置验证失败');
result.error.errors.forEach(error => {
console.error(bgRed(` ${error.path.join('.')}: ${error.message} `));
});
process.exit(1);
}
await fs.writeFile(
path.join(process.cwd(), 'config.json'),
JSON.stringify(result.data, null, 2)
);
outro('配置文件保存成功!');
} catch (error) {
cancel('保存配置文件时出错');
if (error instanceof Error) {
console.error(bgRed(` ${error.message} `));
}
process.exit(1);
}
}
main().catch(error => {
cancel('发生意外错误');
if (error instanceof Error) {
console.error(bgRed(` ${error.message} `));
}
process.exit(1);
});

86
scripts/dev.ts Normal file
View File

@ -0,0 +1,86 @@
/**
* @author Taoya
* @date 2022/5/23
* @Description:
*/
import * as fs from 'fs';
import { resolve } from 'path';
import { exec, ChildProcess } from 'child_process';
import chalk from 'chalk';
import logger from 'consola'
interface BuildResult {
success: boolean;
message: string;
}
async function buildAndroid(): Promise<BuildResult> {
return new Promise((resolve) => {
const buildProcess = exec('npx cordova run android', (error, stdout, stderr) => {
if (error) {
resolve({
success: false,
message: error.message
});
return;
}
if (stdout.includes('BUILD SUCCESSFUL')) {
resolve({
success: true,
message: 'BUILD SUCCESSFUL'
});
} else {
resolve({
success: false,
message: stderr || '构建失败'
});
}
});
// 输出构建日志
buildProcess.stdout?.on('data', (data: string) => {
console.log(chalk.green(data));
});
});
}
async function main(): Promise<void> {
try {
const packageName: string = 'dev.apk';
const distPath: string = './dist';
const outputPath: string = './platforms/android/app/build/outputs/apk/debug/app-debug.apk';
// 检查dist目录是否存在不存在则创建
if (!fs.existsSync(distPath)) {
fs.mkdirSync(distPath, { recursive: true });
}
// 构建android应用
const buildResult = await buildAndroid();
if (buildResult.success) {
logger.log('info', '构建成功');
// 如果目标文件已存在则删除
if (fs.existsSync(`${distPath}/${packageName}`)) {
fs.unlinkSync(`${distPath}/${packageName}`);
}
// 移动构建产物
fs.renameSync(outputPath, `${distPath}/${packageName}`);
} else {
logger.log('error', `构建失败: ${buildResult.message}`);
process.exit(1);
}
} catch (error) {
logger.log('error', `执行失败: ${error instanceof Error ? error.message : '未知错误'}`);
process.exit(1);
}
}
// 执行主函数
main().catch(error => {
logger.log('error', `意外错误: ${error instanceof Error ? error.message : '未知错误'}`);
process.exit(1);
});

146
scripts/logo.ts Normal file
View File

@ -0,0 +1,146 @@
import * as fs from 'fs';
import { resolve } from 'path';
import { execSync } from 'child_process';
import * as path from 'path';
import consola from 'consola';
import axios from 'axios';
import { URL } from 'url';
// 错误信息常量
const ErrorMessages = {
NO_SOURCE: '请提供源文件路径或URL',
INVALID_FILE_TYPE: '无效的文件类型,仅支持 PNG、JPG、JPEG 和 GIF',
FILE_NOT_EXIST: '源文件不存在',
DOWNLOAD_FAILED: '文件下载失败:',
COPY_FAILED: '文件复制失败:',
UNEXPECTED_ERROR: '发生意外错误:',
};
/**
* URL下载文件到指定目录
* @param url URL地址
* @param destination
*/
async function downloadFile(url: string, destination: string): Promise<void> {
try {
const response = await axios({
url,
method: 'GET',
responseType: 'stream'
});
const writer = fs.createWriteStream(destination);
response.data.pipe(writer);
return new Promise((resolve, reject) => {
writer.on('finish', resolve);
writer.on('error', reject);
});
} catch (error) {
throw new Error(`${ErrorMessages.DOWNLOAD_FAILED} ${error.message}`);
}
}
/**
*
* @param source
* @param destination
*/
function copy(source: string, destination: string): void {
try {
execSync(`cp ${source} ${destination}`);
} catch (error) {
throw new Error(`${ErrorMessages.COPY_FAILED} ${error.message}`);
}
}
/**
* URL
* @param string
* @returns boolean URL
*/
function isValidUrl(string: string): boolean {
try {
new URL(string);
return true;
} catch {
return false;
}
}
/**
*
* @param filePath
* @returns boolean
*/
function isValidImageFile(filePath: string): boolean {
const validExtensions = ['.png', '.jpg', '.jpeg', '.gif'];
const ext = path.extname(filePath).toLowerCase();
return validExtensions.includes(ext);
}
/**
* - Logo替换逻辑
*/
async function main(): Promise<void> {
try {
// 获取命令行参数中的源文件路径
const sourcePath = process.argv[2];
if (!sourcePath) {
throw new Error(ErrorMessages.NO_SOURCE);
}
const baseDir: string = resolve('./');
const destinationPath = './res/icon/android/logo.png';
// 打印初始信息
consola.info('开始替换Logo');
consola.info('当前目录: ' + baseDir);
consola.info('源文件: ' + sourcePath);
// 验证文件类型
if (!isValidImageFile(sourcePath)) {
throw new Error(ErrorMessages.INVALID_FILE_TYPE);
}
// 根据源文件类型处理URL或本地文件
if (isValidUrl(sourcePath)) {
consola.info('正在从URL下载文件...');
const tempFile = path.join(baseDir, 'temp-logo' + path.extname(sourcePath));
try {
// 下载并替换文件
await downloadFile(sourcePath, tempFile);
copy(tempFile, destinationPath);
consola.success('Logo替换成功');
} finally {
// 清理临时文件
if (fs.existsSync(tempFile)) {
fs.unlinkSync(tempFile);
}
}
} else {
// 处理本地文件
const localPath = path.resolve(sourcePath);
if (!fs.existsSync(localPath)) {
throw new Error(ErrorMessages.FILE_NOT_EXIST);
}
// 复制文件
copy(localPath, destinationPath);
consola.success('Logo替换成功');
}
} catch (error) {
consola.error('Logo替换失败:', error.message);
process.exit(1);
}
}
// 执行主函数
main().catch(error => {
consola.error(ErrorMessages.UNEXPECTED_ERROR, error);
process.exit(1);
});

175
www/index.html Normal file
View File

@ -0,0 +1,175 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="initial-scale=1, width=device-width, viewport-fit=cover"
/>
<title>Hello World</title>
<script src="cordova.js"></script>
<!-- 导入样式 -->
<link rel="stylesheet" href="//unpkg.com/element-plus/dist/index.css" />
<!-- 导入 Vue 3 -->
<script src="//unpkg.com/vue@next"></script>
<!-- 导入组件库 -->
<script src="//unpkg.com/element-plus"></script>
</head>
<body>
<div id="app">
<h1>Cordova plugins</h1>
<el-button @click="getDeviceInfo">获取设备信息</el-button>
<el-button @click="openUrl">打开百度</el-button>
<el-button @click="deviceVibration">设备震动</el-button>
<el-button @click="scanCode">扫码</el-button>
<el-button @click="takPhoto">拍照</el-button>
<el-button @click="docPrint">文件打印</el-button>
<el-button @click="versionCheck">版本升级</el-button>
<el-button @click="installAPK" type="danger">安装apk</el-button>
<el-button @click="customPlugin">自定义插件</el-button>
</div>
</body>
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<script>
var vConsole = new window.VConsole();
</script>
<script>
const app = Vue.createApp({
mounted() {
setTimeout(() => {
window.addEventListener("batterystatus", this.onBatteryStatus, false);
}, 600);
},
methods: {
customPlugin() {
console.log(cordovaTest);
cordovaTest.coolMethod(
"param",
(res) => {
console.log(res);
this.$message.success(res);
},
(e) => {
this.$message.error(e);
}
);
},
installAPK() {
let url = "http://3000.taoya.art/app.apk";
var filename = url.split("/").pop();
var targetPath = cordova.file.externalRootDirectory + filename;
console.log(cordova.file.externalRootDirectory);
//1. 下载
let _this = this;
_this.progress = 0;
_this.fileTransfer = new FileTransfer();
_this.fileTransfer.onprogress = function (e) {
if (e.lengthComputable) {
const progress = e.loaded / e.total;
_this.progress = (progress * 100).toFixed(2);
console.log(progress);
}
};
console.log("download file");
_this.fileTransfer.download(
encodeURI(url), //服务器地址
targetPath, //本地存储地址
function (entry) {
//下载完成回调
let url = entry.toURL();
console.log(url);
},
function (error) {
console.log(error);
_this.$message.error(error);
},
{
trustHosts: true,
}
);
return;
//2. 安装
cordova.plugins.fileOpener2.open(
"",
"application/vnd.android.package-archive",
{
error: function (e) {
console.log("open fail");
},
success: function () {
console.log("open successfully");
},
}
);
},
async versionCheck() {
let versionCode =
await window.cordova.getAppVersion.getVersionNumber();
this.$message.success(`当前版本: ${versionCode}`);
},
docPrint() {
// use
// cordova.plugins.printer.print();
cordova.plugins.printer.print("<b>Hello Cordova!</b>");
},
takPhoto() {
let that = this;
window.navigator.camera.getPicture(
(imageURI) => {
console.log(imageURI);
that.$message.success(`文件路径: ${imageURI}`);
},
(message) => {
this.$message.warning("取消拍照");
},
{
quality: 50,
destinationType: Camera.DestinationType.FILE_URI,
}
);
},
scanCode() {
cordova.plugins.barcodeScanner.scan(
function (result) {
console.log("扫码结果", result);
try {
if (result.text) {
alert(result.text);
}
} catch (e) {
console.log(e);
}
},
function (error) {
that.$message.error("扫描失败");
}
);
},
onBatteryStatus(status) {
this.$message.warning(
"电池电量: " + status.level + " 是否充电中: " + status.isPlugged
);
},
deviceVibration() {
navigator.vibrate(600);
// var pattern = [1000, 1000, 1000, 1000];
// navigator.vibrate(pattern);
},
openUrl() {
var ref = cordova.InAppBrowser.open(
"https://www.baidu.com",
"_blank",
"location=yes"
);
},
getDeviceInfo() {
this.$message.success("请打开控制台查看");
console.log(window.device);
},
},
});
app.use(ElementPlus);
app.mount("#app");
</script>
</html>