- 前端:微信小程序,支持OCR识别和课程管理 - 后端:Spring Boot,提供API服务和数据管理 - 功能:表格OCR识别、时间冲突检测、周次管理 - 文档:完整的使用教程和技术说明master
commit
0139983fc3
@ -0,0 +1,78 @@
|
||||
# Java
|
||||
*.class
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
*.nar
|
||||
hs_err_pid*
|
||||
replay_pid*
|
||||
|
||||
# Maven
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
.vscode/
|
||||
.settings/
|
||||
.project
|
||||
.classpath
|
||||
|
||||
# Database
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
*.log.*
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Node.js (for miniprogram)
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# WeChat MiniProgram
|
||||
miniprogram_npm/
|
||||
.tea/
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
build/
|
||||
|
||||
# Configuration files with sensitive data
|
||||
application-prod.yml
|
||||
application-local.yml
|
||||
|
||||
# Backup files
|
||||
*.bak
|
||||
*.backup
|
@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.7.18</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.scheduleocr</groupId>
|
||||
<artifactId>schedule-ocr-backend</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>schedule-ocr-backend</name>
|
||||
<description>大学生课表OCR识别小程序后端服务</description>
|
||||
|
||||
<properties>
|
||||
<java.version>8</java.version>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot Web Starter -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Data JPA -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- SQLite JDBC Driver -->
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.42.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 百度OCR SDK -->
|
||||
<dependency>
|
||||
<groupId>com.baidu.aip</groupId>
|
||||
<artifactId>java-sdk</artifactId>
|
||||
<version>4.16.8</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON处理 -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 文件上传处理 -->
|
||||
<dependency>
|
||||
<groupId>commons-fileupload</groupId>
|
||||
<artifactId>commons-fileupload</artifactId>
|
||||
<version>1.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Commons IO -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.11.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Validation -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,74 @@
|
||||
package com.scheduleocr.config;
|
||||
|
||||
import com.baidu.aip.ocr.AipOcr;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 百度OCR配置类
|
||||
*
|
||||
* @author scheduleocr
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "baidu.ocr")
|
||||
public class BaiduOcrConfig {
|
||||
|
||||
/**
|
||||
* 百度OCR应用ID
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 百度OCR API Key
|
||||
*/
|
||||
private String apiKey;
|
||||
|
||||
/**
|
||||
* 百度OCR Secret Key
|
||||
*/
|
||||
private String secretKey;
|
||||
|
||||
// Getters and Setters
|
||||
public String getAppId() {
|
||||
return appId;
|
||||
}
|
||||
|
||||
public void setAppId(String appId) {
|
||||
this.appId = appId;
|
||||
}
|
||||
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public void setApiKey(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
public String getSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
public void setSecretKey(String secretKey) {
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建百度OCR客户端Bean
|
||||
*
|
||||
* @return AipOcr客户端
|
||||
*/
|
||||
@Bean
|
||||
public AipOcr aipOcr() {
|
||||
// 初始化一个AipOcr
|
||||
AipOcr client = new AipOcr(appId, apiKey, secretKey);
|
||||
|
||||
// 可选:设置网络连接参数
|
||||
client.setConnectionTimeoutInMillis(2000);
|
||||
client.setSocketTimeoutInMillis(60000);
|
||||
|
||||
return client;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package com.scheduleocr.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* 跨域配置类
|
||||
* 支持微信小程序调用后端API
|
||||
*
|
||||
* @author scheduleocr
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@Configuration
|
||||
public class CorsConfig implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 配置跨域访问
|
||||
*/
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/api/**")
|
||||
.allowedOriginPatterns("*") // 允许所有域名访问
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
.allowedHeaders("*")
|
||||
.allowCredentials(true)
|
||||
.maxAge(3600); // 预检请求的缓存时间
|
||||
}
|
||||
|
||||
/**
|
||||
* CORS配置源
|
||||
*/
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
|
||||
// 允许所有域名
|
||||
configuration.addAllowedOriginPattern("*");
|
||||
|
||||
// 允许的HTTP方法
|
||||
configuration.addAllowedMethod("GET");
|
||||
configuration.addAllowedMethod("POST");
|
||||
configuration.addAllowedMethod("PUT");
|
||||
configuration.addAllowedMethod("DELETE");
|
||||
configuration.addAllowedMethod("OPTIONS");
|
||||
|
||||
// 允许的请求头
|
||||
configuration.addAllowedHeader("*");
|
||||
|
||||
// 允许发送Cookie
|
||||
configuration.setAllowCredentials(true);
|
||||
|
||||
// 预检请求的缓存时间
|
||||
configuration.setMaxAge(3600L);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/api/**", configuration);
|
||||
|
||||
return source;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.scheduleocr.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/**
|
||||
* 数据库配置类
|
||||
*
|
||||
* @author scheduleocr
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@Configuration
|
||||
@EnableJpaRepositories(basePackages = "com.scheduleocr.repository")
|
||||
@EnableTransactionManagement
|
||||
public class DatabaseConfig {
|
||||
|
||||
// SQLite数据库配置已在application.yml中完成
|
||||
// 这里主要用于启用JPA仓库和事务管理
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package com.scheduleocr.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.Statement;
|
||||
|
||||
/**
|
||||
* 数据库初始化器
|
||||
* 在应用启动时创建必要的数据库表
|
||||
*
|
||||
* @author scheduleocr
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@Component
|
||||
public class DatabaseInitializer implements CommandLineRunner {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DatabaseInitializer.class);
|
||||
|
||||
@Autowired
|
||||
private DataSource dataSource;
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
log.info("开始初始化数据库表...");
|
||||
|
||||
try (Connection connection = dataSource.getConnection();
|
||||
Statement statement = connection.createStatement()) {
|
||||
|
||||
// 创建courses表
|
||||
String createTableSQL = "CREATE TABLE IF NOT EXISTS courses (" +
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
"user_openid VARCHAR(100) NOT NULL, " +
|
||||
"course_name VARCHAR(100) NOT NULL, " +
|
||||
"teacher_name VARCHAR(50), " +
|
||||
"classroom VARCHAR(100) NOT NULL, " +
|
||||
"day_of_week INTEGER NOT NULL CHECK (day_of_week >= 1 AND day_of_week <= 7), " +
|
||||
"start_time VARCHAR(10) NOT NULL, " +
|
||||
"end_time VARCHAR(10) NOT NULL, " +
|
||||
"notes VARCHAR(500), " +
|
||||
"start_week INTEGER, " +
|
||||
"end_week INTEGER, " +
|
||||
"week_type INTEGER, " +
|
||||
"create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " +
|
||||
"update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP" +
|
||||
")";
|
||||
|
||||
statement.execute(createTableSQL);
|
||||
log.info("courses表创建成功");
|
||||
|
||||
// 创建索引
|
||||
String createIndexSQL1 = "CREATE INDEX IF NOT EXISTS idx_courses_user_openid ON courses(user_openid)";
|
||||
String createIndexSQL2 = "CREATE INDEX IF NOT EXISTS idx_courses_day_time ON courses(day_of_week, start_time)";
|
||||
|
||||
statement.execute(createIndexSQL1);
|
||||
statement.execute(createIndexSQL2);
|
||||
log.info("数据库索引创建成功");
|
||||
|
||||
// 插入一些测试数据
|
||||
insertTestData(statement);
|
||||
|
||||
log.info("数据库初始化完成!");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("数据库初始化失败", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void insertTestData(Statement statement) throws Exception {
|
||||
// 检查是否已有数据
|
||||
java.sql.ResultSet resultSet = statement.executeQuery("SELECT COUNT(*) FROM courses");
|
||||
if (resultSet.next() && resultSet.getInt(1) > 0) {
|
||||
log.info("数据库中已有数据,跳过测试数据插入");
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("插入测试数据...");
|
||||
|
||||
String[] testData = {
|
||||
"INSERT INTO courses (user_openid, course_name, teacher_name, classroom, day_of_week, start_time, end_time, notes) VALUES " +
|
||||
"('test_user_001', '高等数学', '张教授', '教学楼A101', 1, '08:00', '09:40', '第1-16周')",
|
||||
|
||||
"INSERT INTO courses (user_openid, course_name, teacher_name, classroom, day_of_week, start_time, end_time, notes) VALUES " +
|
||||
"('test_user_001', '大学英语', '李老师', '教学楼B203', 1, '10:00', '11:40', '第1-16周')",
|
||||
|
||||
"INSERT INTO courses (user_openid, course_name, teacher_name, classroom, day_of_week, start_time, end_time, notes) VALUES " +
|
||||
"('test_user_001', '计算机程序设计', '王教授', '实验楼C301', 3, '14:00', '15:40', '第1-16周')",
|
||||
|
||||
"INSERT INTO courses (user_openid, course_name, teacher_name, classroom, day_of_week, start_time, end_time, notes) VALUES " +
|
||||
"('test_user_001', '数据结构', '赵老师', '教学楼A205', 5, '08:00', '09:40', '第1-16周')"
|
||||
};
|
||||
|
||||
for (String sql : testData) {
|
||||
statement.execute(sql);
|
||||
}
|
||||
|
||||
log.info("测试数据插入完成");
|
||||
}
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
package com.scheduleocr.config;
|
||||
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.dialect.function.SQLFunctionTemplate;
|
||||
import org.hibernate.dialect.function.StandardSQLFunction;
|
||||
import org.hibernate.dialect.function.VarArgsSQLFunction;
|
||||
import org.hibernate.dialect.identity.IdentityColumnSupport;
|
||||
import org.hibernate.dialect.identity.IdentityColumnSupportImpl;
|
||||
import org.hibernate.type.StringType;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* SQLite数据库方言
|
||||
* 适配Spring Boot 2.7的Hibernate版本
|
||||
*
|
||||
* @author scheduleocr
|
||||
* @version 1.0.0
|
||||
*/
|
||||
public class SQLiteDialect extends Dialect {
|
||||
|
||||
public SQLiteDialect() {
|
||||
// 注册列类型映射
|
||||
registerColumnType(Types.BIT, "integer");
|
||||
registerColumnType(Types.TINYINT, "tinyint");
|
||||
registerColumnType(Types.SMALLINT, "smallint");
|
||||
registerColumnType(Types.INTEGER, "integer");
|
||||
registerColumnType(Types.BIGINT, "bigint");
|
||||
registerColumnType(Types.FLOAT, "float");
|
||||
registerColumnType(Types.REAL, "real");
|
||||
registerColumnType(Types.DOUBLE, "double");
|
||||
registerColumnType(Types.NUMERIC, "numeric");
|
||||
registerColumnType(Types.DECIMAL, "decimal");
|
||||
registerColumnType(Types.CHAR, "char");
|
||||
registerColumnType(Types.VARCHAR, "varchar");
|
||||
registerColumnType(Types.LONGVARCHAR, "longvarchar");
|
||||
registerColumnType(Types.DATE, "date");
|
||||
registerColumnType(Types.TIME, "time");
|
||||
registerColumnType(Types.TIMESTAMP, "timestamp");
|
||||
registerColumnType(Types.BINARY, "blob");
|
||||
registerColumnType(Types.VARBINARY, "blob");
|
||||
registerColumnType(Types.LONGVARBINARY, "blob");
|
||||
registerColumnType(Types.BLOB, "blob");
|
||||
registerColumnType(Types.CLOB, "clob");
|
||||
registerColumnType(Types.BOOLEAN, "integer");
|
||||
|
||||
// 注册函数
|
||||
registerFunction("concat", new VarArgsSQLFunction(StringType.INSTANCE, "", "||", ""));
|
||||
registerFunction("mod", new SQLFunctionTemplate(StringType.INSTANCE, "?1 % ?2"));
|
||||
registerFunction("substr", new StandardSQLFunction("substr", StringType.INSTANCE));
|
||||
registerFunction("substring", new StandardSQLFunction("substr", StringType.INSTANCE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityColumnSupport getIdentityColumnSupport() {
|
||||
return new SQLiteIdentityColumnSupport();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAlterTable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dropConstraints() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDropForeignKeyString() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAddForeignKeyConstraintString(String constraintName,
|
||||
String[] foreignKey, String referencedTable, String[] primaryKey,
|
||||
boolean referencesPrimaryKey) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAddPrimaryKeyConstraintString(String constraintName) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsIfExistsBeforeTableName() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCascadeDelete() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCurrentTimestampSelection() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentTimestampSelectString() {
|
||||
return "select current_timestamp";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrentTimestampSelectStringCallable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* SQLite身份列支持
|
||||
*/
|
||||
public static class SQLiteIdentityColumnSupport extends IdentityColumnSupportImpl {
|
||||
|
||||
@Override
|
||||
public boolean supportsIdentityColumns() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentitySelectString(String table, String column, int type) {
|
||||
return "select last_insert_rowid()";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentityColumnString(int type) {
|
||||
return "integer primary key autoincrement";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasDataTypeInIdentityColumn() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package com.scheduleocr.controller;
|
||||
|
||||
import com.scheduleocr.dto.ApiResponse;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 健康检查控制器
|
||||
* 提供系统状态检查接口
|
||||
*
|
||||
* @author scheduleocr
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class HealthController {
|
||||
|
||||
/**
|
||||
* 健康检查接口
|
||||
* GET /api/health
|
||||
*
|
||||
* @return 系统状态信息
|
||||
*/
|
||||
@GetMapping("/health")
|
||||
public ApiResponse<Map<String, Object>> health() {
|
||||
Map<String, Object> status = new HashMap<>();
|
||||
status.put("status", "UP");
|
||||
status.put("timestamp", LocalDateTime.now());
|
||||
status.put("service", "schedule-ocr-backend");
|
||||
status.put("version", "1.0.0");
|
||||
|
||||
return ApiResponse.success("系统运行正常", status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统信息接口
|
||||
* GET /api/info
|
||||
*
|
||||
* @return 系统详细信息
|
||||
*/
|
||||
@GetMapping("/info")
|
||||
public ApiResponse<Map<String, Object>> info() {
|
||||
Map<String, Object> info = new HashMap<>();
|
||||
info.put("application", "大学生课表OCR识别小程序后端服务");
|
||||
info.put("version", "1.0.0");
|
||||
info.put("description", "支持课程表管理和OCR图片识别功能");
|
||||
info.put("author", "scheduleocr");
|
||||
info.put("timestamp", LocalDateTime.now());
|
||||
|
||||
// JVM信息
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
Map<String, Object> jvm = new HashMap<>();
|
||||
jvm.put("totalMemory", runtime.totalMemory() / 1024 / 1024 + " MB");
|
||||
jvm.put("freeMemory", runtime.freeMemory() / 1024 / 1024 + " MB");
|
||||
jvm.put("maxMemory", runtime.maxMemory() / 1024 / 1024 + " MB");
|
||||
jvm.put("processors", runtime.availableProcessors());
|
||||
info.put("jvm", jvm);
|
||||
|
||||
return ApiResponse.success("获取系统信息成功", info);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages/index/index",
|
||||
"pages/course-add/course-add",
|
||||
"pages/ocr-import/ocr-import",
|
||||
"pages/profile/profile"
|
||||
],
|
||||
"window": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarTitleText": "课表助手",
|
||||
"navigationBarBackgroundColor": "#1296db",
|
||||
"backgroundColor": "#f8f8f8",
|
||||
"backgroundTextStyle": "light"
|
||||
},
|
||||
|
||||
"tabBar": {
|
||||
"color": "#999999",
|
||||
"selectedColor": "#1296db",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderStyle": "black",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"text": "首页",
|
||||
"iconPath": "images/tab-home.png",
|
||||
"selectedIconPath": "images/tab-home-active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/profile/profile",
|
||||
"text": "个人中心",
|
||||
"iconPath": "images/tab-profile.png",
|
||||
"selectedIconPath": "images/tab-profile-active.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"style": "v2",
|
||||
"componentFramework": "glass-easel",
|
||||
"lazyCodeLoading": "requiredComponents"
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
// app.ts
|
||||
App<IAppOption>({
|
||||
globalData: {},
|
||||
onLaunch() {
|
||||
// 展示本地存储能力
|
||||
const logs = wx.getStorageSync('logs') || []
|
||||
logs.unshift(Date.now())
|
||||
wx.setStorageSync('logs', logs)
|
||||
|
||||
// 登录
|
||||
wx.login({
|
||||
success: res => {
|
||||
console.log(res.code)
|
||||
// 发送 res.code 到后台换取 openId, sessionKey, unionId
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
@ -0,0 +1,61 @@
|
||||
/**app.wxss**/
|
||||
|
||||
/* 全局样式重置 */
|
||||
page {
|
||||
height: 100%;
|
||||
background-color: #f8f8f8;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* 通用容器 */
|
||||
.container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 通用文本样式 */
|
||||
.text-primary {
|
||||
color: #1296db;
|
||||
}
|
||||
|
||||
.text-secondary {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #ff4757;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: #2ed573;
|
||||
}
|
||||
|
||||
/* 通用布局 */
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.7 KiB |
@ -0,0 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "添加课程",
|
||||
"navigationBarBackgroundColor": "#1296db",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
<!--course-add.wxml-->
|
||||
<view class="container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<text class="page-title">{{mode === 'edit' ? '编辑课程' : '添加课程'}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<scroll-view class="form-content" scroll-y="true">
|
||||
<view class="form-section">
|
||||
<!-- 课程名称 -->
|
||||
<view class="form-item">
|
||||
<text class="label">课程名称 *</text>
|
||||
<input class="input"
|
||||
placeholder="请输入课程名称"
|
||||
value="{{formData.courseName}}"
|
||||
bindinput="onCourseNameInput" />
|
||||
</view>
|
||||
|
||||
<!-- 上课地点 -->
|
||||
<view class="form-item">
|
||||
<text class="label">上课地点</text>
|
||||
<input class="input"
|
||||
placeholder="请输入上课地点"
|
||||
value="{{formData.classroom}}"
|
||||
bindinput="onClassroomInput" />
|
||||
</view>
|
||||
|
||||
<!-- 任课教师 -->
|
||||
<view class="form-item">
|
||||
<text class="label">任课教师</text>
|
||||
<input class="input"
|
||||
placeholder="请输入任课教师"
|
||||
value="{{formData.teacherName}}"
|
||||
bindinput="onTeacherNameInput" />
|
||||
</view>
|
||||
|
||||
<!-- 星期几 -->
|
||||
<view class="form-item">
|
||||
<text class="label">星期几 *</text>
|
||||
<view class="week-selector">
|
||||
<view class="week-option {{formData.dayOfWeek === index + 1 ? 'selected' : ''}}"
|
||||
wx:for="{{weekOptions}}"
|
||||
wx:key="index"
|
||||
bindtap="onWeekSelect"
|
||||
data-day="{{index + 1}}">
|
||||
<text class="week-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 上课时间 -->
|
||||
<view class="form-item">
|
||||
<text class="label">上课时间 *</text>
|
||||
<view class="time-slot-selector">
|
||||
<view class="time-slot-option {{formData.timeSlot === item.value ? 'selected' : ''}}"
|
||||
wx:for="{{timeSlots}}"
|
||||
wx:key="value"
|
||||
bindtap="onTimeSlotSelect"
|
||||
data-index="{{index}}">
|
||||
<text class="time-slot-name">{{item.name}}</text>
|
||||
<text class="time-slot-time">{{item.time}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 周次 -->
|
||||
<view class="form-item">
|
||||
<text class="label">周次</text>
|
||||
<view class="week-range-container">
|
||||
<!-- 周次范围输入 -->
|
||||
<view class="week-range-inputs">
|
||||
<view class="week-input-group">
|
||||
<text class="week-label">第</text>
|
||||
<input class="week-input"
|
||||
type="number"
|
||||
value="{{formData.startWeek}}"
|
||||
bindinput="onStartWeekInput"
|
||||
placeholder="1" />
|
||||
<text class="week-label">周</text>
|
||||
</view>
|
||||
<text class="week-separator">至</text>
|
||||
<view class="week-input-group">
|
||||
<text class="week-label">第</text>
|
||||
<input class="week-input"
|
||||
type="number"
|
||||
value="{{formData.endWeek}}"
|
||||
bindinput="onEndWeekInput"
|
||||
placeholder="16" />
|
||||
<text class="week-label">周</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 单双周选择 -->
|
||||
<view class="week-type-selector">
|
||||
<view class="week-type-option {{formData.weekType === item.value ? 'selected' : ''}}"
|
||||
wx:for="{{weekTypeOptions}}"
|
||||
wx:key="value"
|
||||
bindtap="onWeekTypeSelect"
|
||||
data-type="{{item.value}}">
|
||||
<text class="week-type-text">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 周次预览 -->
|
||||
<view class="week-preview">
|
||||
<text class="preview-label">预览:</text>
|
||||
<text class="preview-text">{{formData.weeks || '1-16周'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 备注 -->
|
||||
<view class="form-item">
|
||||
<text class="label">备注</text>
|
||||
<textarea class="textarea"
|
||||
placeholder="请输入备注信息"
|
||||
value="{{formData.notes}}"
|
||||
bindinput="onNotesInput"
|
||||
maxlength="200" />
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<view class="bottom-actions">
|
||||
<button class="action-btn secondary" bindtap="onCancel">
|
||||
<text class="btn-text">取消</text>
|
||||
</button>
|
||||
<button class="action-btn primary"
|
||||
bindtap="onSave"
|
||||
disabled="{{!canSave}}">
|
||||
<text class="btn-text">{{mode === 'edit' ? '保存' : '添加'}}</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 删除按钮(仅编辑模式显示) -->
|
||||
<view wx:if="{{mode === 'edit'}}" class="delete-section">
|
||||
<button class="delete-btn" bindtap="onDelete">
|
||||
<text class="btn-text">删除课程</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
@ -0,0 +1,313 @@
|
||||
/**course-add.wxss**/
|
||||
.container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
/* 页面标题 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #1296db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 表单内容 */
|
||||
.form-content {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background-color: white;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.form-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
padding: 0 20rpx;
|
||||
border: 1rpx solid #e5e5e5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
background-color: #fafafa;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
border-color: #1296db;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
min-height: 120rpx;
|
||||
padding: 20rpx;
|
||||
border: 1rpx solid #e5e5e5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
background-color: #fafafa;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.textarea:focus {
|
||||
border-color: #1296db;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* 星期选择器 */
|
||||
.week-selector {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.week-option {
|
||||
flex: 1;
|
||||
min-width: 80rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1rpx solid #e5e5e5;
|
||||
border-radius: 8rpx;
|
||||
background-color: #fafafa;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.week-option.selected {
|
||||
background-color: #1296db;
|
||||
border-color: #1296db;
|
||||
}
|
||||
|
||||
.week-text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.week-option.selected .week-text {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 时间段选择器 */
|
||||
.time-slot-selector {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
max-height: 400rpx;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.time-slot-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 80rpx;
|
||||
padding: 0 20rpx;
|
||||
border: 1rpx solid #e5e5e5;
|
||||
border-radius: 8rpx;
|
||||
background-color: #fafafa;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.time-slot-option.selected {
|
||||
background-color: #1296db;
|
||||
border-color: #1296db;
|
||||
}
|
||||
|
||||
.time-slot-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.time-slot-time {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.time-slot-option.selected .time-slot-name,
|
||||
.time-slot-option.selected .time-slot-time {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 周次选择器 */
|
||||
.week-range-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.week-range-inputs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.week-input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.week-label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.week-input {
|
||||
width: 80rpx;
|
||||
height: 60rpx;
|
||||
text-align: center;
|
||||
border: 1rpx solid #e5e5e5;
|
||||
border-radius: 6rpx;
|
||||
font-size: 26rpx;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.week-input:focus {
|
||||
border-color: #1296db;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.week-separator {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.week-type-selector {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.week-type-option {
|
||||
flex: 1;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1rpx solid #e5e5e5;
|
||||
border-radius: 8rpx;
|
||||
background-color: #fafafa;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.week-type-option.selected {
|
||||
background-color: #1296db;
|
||||
border-color: #1296db;
|
||||
}
|
||||
|
||||
.week-type-text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.week-type-option.selected .week-type-text {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.week-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
background-color: #f0f8ff;
|
||||
border-radius: 8rpx;
|
||||
border: 1rpx solid #e6f3ff;
|
||||
}
|
||||
|
||||
.preview-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.preview-text {
|
||||
font-size: 26rpx;
|
||||
color: #1296db;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 底部操作按钮 */
|
||||
.bottom-actions {
|
||||
display: flex;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: white;
|
||||
border-top: 1rpx solid #e5e5e5;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 30rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.action-btn.primary {
|
||||
background: linear-gradient(135deg, #1296db 0%, #0d7ec7 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.primary[disabled] {
|
||||
background: #ccc;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.action-btn.secondary {
|
||||
background-color: #f8f8f8;
|
||||
color: #1296db;
|
||||
border: 1rpx solid #1296db;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
/* 删除按钮 */
|
||||
.delete-section {
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
background-color: #ff4757;
|
||||
color: white;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 30rpx;
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
}
|
||||
}
|
@ -0,0 +1,390 @@
|
||||
/**index.wxss**/
|
||||
.container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
/* 页面标题 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #1296db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 周次选择器 */
|
||||
.week-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.week-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.week-btn:active {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.current-week {
|
||||
font-size: 28rpx;
|
||||
opacity: 0.9;
|
||||
min-width: 120rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 星期标题栏 */
|
||||
.week-header {
|
||||
display: flex;
|
||||
background-color: white;
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
}
|
||||
|
||||
.time-header {
|
||||
width: 140rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
background-color: #f5f5f5;
|
||||
border-right: 1rpx solid #e5e5e5;
|
||||
}
|
||||
|
||||
.week-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.week-day {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.week-date {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 课程表内容 */
|
||||
.schedule-content {
|
||||
flex: 1;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.schedule-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.schedule-row {
|
||||
display: flex;
|
||||
min-height: 120rpx;
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
}
|
||||
|
||||
/* 时间段列 */
|
||||
.time-slot-cell {
|
||||
width: 140rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-right: 1rpx solid #e5e5e5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8rpx;
|
||||
}
|
||||
|
||||
.time-slot-name {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.time-slot-time {
|
||||
font-size: 20rpx;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 课程单元格 */
|
||||
.course-cell {
|
||||
flex: 1;
|
||||
border-right: 1rpx solid #e5e5e5;
|
||||
position: relative;
|
||||
min-height: 120rpx;
|
||||
}
|
||||
|
||||
.course-item {
|
||||
position: absolute;
|
||||
top: 4rpx;
|
||||
left: 4rpx;
|
||||
right: 4rpx;
|
||||
bottom: 4rpx;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 8rpx;
|
||||
padding: 8rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.course-name {
|
||||
font-size: 22rpx;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.course-location {
|
||||
font-size: 18rpx;
|
||||
color: rgba(255,255,255,0.9);
|
||||
margin-bottom: 2rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.course-teacher {
|
||||
font-size: 18rpx;
|
||||
color: rgba(255,255,255,0.8);
|
||||
margin-bottom: 2rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.course-weeks {
|
||||
font-size: 16rpx;
|
||||
color: rgba(255,255,255,0.7);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 600rpx;
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 32rpx;
|
||||
color: #999;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
font-size: 28rpx;
|
||||
color: #ccc;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 底部操作按钮 */
|
||||
.bottom-actions {
|
||||
display: flex;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: white;
|
||||
border-top: 1rpx solid #e5e5e5;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 30rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.action-btn.primary {
|
||||
background: linear-gradient(135deg, #1296db 0%, #0d7ec7 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.secondary {
|
||||
background-color: #f8f8f8;
|
||||
color: #1296db;
|
||||
border: 1rpx solid #1296db;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
/* 课程详情弹窗 */
|
||||
.course-detail-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.course-detail-content {
|
||||
width: 600rpx;
|
||||
max-height: 80vh;
|
||||
background: white;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
animation: slideUp 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(100rpx);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 40rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.detail-body {
|
||||
padding: 30rpx;
|
||||
max-height: 500rpx;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
margin-bottom: 24rpx;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.detail-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
width: 140rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.detail-actions {
|
||||
display: flex;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.detail-btn {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 30rpx;
|
||||
border: none;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.detail-btn.edit {
|
||||
color: #1296db;
|
||||
border-right: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.detail-btn.delete {
|
||||
color: #ff4757;
|
||||
}
|
||||
|
||||
.detail-btn:active {
|
||||
background: #f8f8f8;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "OCR导入",
|
||||
"navigationBarBackgroundColor": "#1296db",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
@ -0,0 +1,384 @@
|
||||
// ocr-import.ts
|
||||
|
||||
// 课程接口定义
|
||||
interface Course {
|
||||
id?: number;
|
||||
courseName: string;
|
||||
classroom: string;
|
||||
teacherName?: string;
|
||||
dayOfWeek: number;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
notes?: string;
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
// OCR结果接口
|
||||
interface OcrResult {
|
||||
success: boolean;
|
||||
rawText?: string;
|
||||
textLines?: string[];
|
||||
courses?: Course[];
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
// 导入结果接口
|
||||
interface ImportResult {
|
||||
successCount: number;
|
||||
errorCount: number;
|
||||
errorMessages?: string[];
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
currentStep: 1, // 1: 选择图片, 2: OCR识别, 3: 导入结果
|
||||
selectedImage: '',
|
||||
ocrLoading: false,
|
||||
importLoading: false,
|
||||
ocrResult: {} as OcrResult,
|
||||
importResult: {} as ImportResult,
|
||||
weekNames: ['一', '二', '三', '四', '五', '六', '日'],
|
||||
hasSelectedCourses: false,
|
||||
|
||||
// 编辑相关
|
||||
showEditModal: false,
|
||||
editingCourse: {} as Course,
|
||||
editingIndex: -1,
|
||||
weekTypeOptions: ['每周', '单周', '双周']
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
// 页面加载时的初始化
|
||||
},
|
||||
|
||||
// 选择图片
|
||||
onChooseImage() {
|
||||
wx.showActionSheet({
|
||||
itemList: ['拍照', '从相册选择'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) {
|
||||
this.takePhoto();
|
||||
} else if (res.tapIndex === 1) {
|
||||
this.chooseFromAlbum();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 拍照
|
||||
takePhoto() {
|
||||
wx.chooseMedia({
|
||||
count: 1,
|
||||
mediaType: ['image'],
|
||||
sourceType: ['camera'],
|
||||
camera: 'back',
|
||||
success: (res) => {
|
||||
const tempFilePath = res.tempFiles[0].tempFilePath;
|
||||
this.setData({
|
||||
selectedImage: tempFilePath
|
||||
});
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error('拍照失败:', error);
|
||||
wx.showToast({
|
||||
title: '拍照失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 从相册选择
|
||||
chooseFromAlbum() {
|
||||
wx.chooseMedia({
|
||||
count: 1,
|
||||
mediaType: ['image'],
|
||||
sourceType: ['album'],
|
||||
success: (res) => {
|
||||
const tempFilePath = res.tempFiles[0].tempFilePath;
|
||||
this.setData({
|
||||
selectedImage: tempFilePath
|
||||
});
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error('选择图片失败:', error);
|
||||
wx.showToast({
|
||||
title: '选择图片失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 开始OCR识别
|
||||
async onStartOcr() {
|
||||
if (!this.data.selectedImage) {
|
||||
wx.showToast({
|
||||
title: '请先选择图片',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData({
|
||||
currentStep: 2,
|
||||
ocrLoading: true
|
||||
});
|
||||
|
||||
try {
|
||||
// 上传图片并进行OCR识别
|
||||
const uploadResult = await this.uploadImageForOcr(this.data.selectedImage);
|
||||
|
||||
if (uploadResult.success) {
|
||||
// 为课程添加选中状态
|
||||
const coursesWithSelection = (uploadResult.courses || []).map(course => ({
|
||||
...course,
|
||||
selected: true
|
||||
}));
|
||||
|
||||
this.setData({
|
||||
ocrResult: {
|
||||
...uploadResult,
|
||||
courses: coursesWithSelection
|
||||
},
|
||||
hasSelectedCourses: coursesWithSelection.length > 0
|
||||
});
|
||||
} else {
|
||||
this.setData({
|
||||
ocrResult: uploadResult
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('OCR识别失败:', error);
|
||||
this.setData({
|
||||
ocrResult: {
|
||||
success: false,
|
||||
errorMessage: '网络错误,请重试'
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
this.setData({
|
||||
ocrLoading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 上传图片进行OCR识别
|
||||
async uploadImageForOcr(imagePath: string): Promise<OcrResult> {
|
||||
try {
|
||||
const api = require('../../utils/api').default;
|
||||
const userOpenid = api.getUserOpenid();
|
||||
|
||||
const res = await api.ocr.uploadImage(imagePath, userOpenid);
|
||||
|
||||
if (res.code === 200) {
|
||||
return res.data;
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
errorMessage: res.message || 'OCR识别失败'
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('OCR识别失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
errorMessage: '网络错误,请重试'
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// 切换课程选中状态
|
||||
onCourseToggle(e: any) {
|
||||
const index = e.currentTarget.dataset.index;
|
||||
const checked = e.detail.value;
|
||||
|
||||
this.setData({
|
||||
[`ocrResult.courses[${index}].selected`]: checked
|
||||
});
|
||||
|
||||
// 检查是否有选中的课程
|
||||
const hasSelected = this.data.ocrResult.courses?.some(course => course.selected) || false;
|
||||
this.setData({
|
||||
hasSelectedCourses: hasSelected
|
||||
});
|
||||
},
|
||||
|
||||
// 导入选中的课程
|
||||
async onImportCourses() {
|
||||
const selectedCourses = this.data.ocrResult.courses?.filter(course => course.selected) || [];
|
||||
|
||||
if (selectedCourses.length === 0) {
|
||||
wx.showToast({
|
||||
title: '请选择要导入的课程',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData({
|
||||
currentStep: 3,
|
||||
importLoading: true
|
||||
});
|
||||
|
||||
try {
|
||||
const api = require('../../utils/api').default;
|
||||
const userOpenid = api.getUserOpenid();
|
||||
|
||||
const res = await api.ocr.importCourses(selectedCourses, userOpenid);
|
||||
|
||||
if (res.code === 200) {
|
||||
const result = res.data;
|
||||
this.setData({
|
||||
importResult: {
|
||||
successCount: result.successCourses?.length || 0,
|
||||
errorCount: result.errorMessages?.length || 0,
|
||||
errorMessages: result.errorMessages || []
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new Error(res.message || '导入失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('导入课程失败:', error);
|
||||
const api = require('../../utils/api').default;
|
||||
api.handleError(error, '导入失败');
|
||||
// 回到上一步
|
||||
this.setData({
|
||||
currentStep: 2
|
||||
});
|
||||
} finally {
|
||||
this.setData({
|
||||
importLoading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 返回第一步
|
||||
onBackToStep1() {
|
||||
this.setData({
|
||||
currentStep: 1,
|
||||
selectedImage: '',
|
||||
ocrResult: {},
|
||||
hasSelectedCourses: false
|
||||
});
|
||||
},
|
||||
|
||||
// 再次导入
|
||||
onImportAgain() {
|
||||
this.setData({
|
||||
currentStep: 1,
|
||||
selectedImage: '',
|
||||
ocrResult: {},
|
||||
importResult: {},
|
||||
hasSelectedCourses: false
|
||||
});
|
||||
},
|
||||
|
||||
// 编辑课程
|
||||
onEditCourse(e: any) {
|
||||
const index = e.currentTarget.dataset.index;
|
||||
const course = this.data.ocrResult.courses[index];
|
||||
|
||||
this.setData({
|
||||
showEditModal: true,
|
||||
editingCourse: { ...course },
|
||||
editingIndex: index
|
||||
});
|
||||
},
|
||||
|
||||
// 关闭编辑弹窗
|
||||
onCloseEditModal() {
|
||||
this.setData({
|
||||
showEditModal: false,
|
||||
editingCourse: {},
|
||||
editingIndex: -1
|
||||
});
|
||||
},
|
||||
|
||||
// 阻止事件冒泡
|
||||
stopPropagation() {
|
||||
// 空方法,用于阻止事件冒泡
|
||||
},
|
||||
|
||||
// 编辑输入
|
||||
onEditInput(e: any) {
|
||||
const field = e.currentTarget.dataset.field;
|
||||
const value = e.detail.value;
|
||||
|
||||
this.setData({
|
||||
[`editingCourse.${field}`]: value
|
||||
});
|
||||
},
|
||||
|
||||
// 星期选择
|
||||
onDayChange(e: any) {
|
||||
const dayOfWeek = parseInt(e.detail.value) + 1;
|
||||
this.setData({
|
||||
'editingCourse.dayOfWeek': dayOfWeek
|
||||
});
|
||||
},
|
||||
|
||||
// 开始时间选择
|
||||
onStartTimeChange(e: any) {
|
||||
this.setData({
|
||||
'editingCourse.startTime': e.detail.value
|
||||
});
|
||||
},
|
||||
|
||||
// 结束时间选择
|
||||
onEndTimeChange(e: any) {
|
||||
this.setData({
|
||||
'editingCourse.endTime': e.detail.value
|
||||
});
|
||||
},
|
||||
|
||||
// 周次类型选择
|
||||
onWeekTypeChange(e: any) {
|
||||
this.setData({
|
||||
'editingCourse.weekType': parseInt(e.detail.value)
|
||||
});
|
||||
},
|
||||
|
||||
// 保存编辑
|
||||
onSaveEdit() {
|
||||
const { editingCourse, editingIndex } = this.data;
|
||||
|
||||
// 验证必填字段
|
||||
if (!editingCourse.courseName?.trim()) {
|
||||
wx.showToast({
|
||||
title: '请输入课程名称',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!editingCourse.classroom?.trim()) {
|
||||
wx.showToast({
|
||||
title: '请输入上课地点',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新课程信息
|
||||
this.setData({
|
||||
[`ocrResult.courses[${editingIndex}]`]: editingCourse,
|
||||
showEditModal: false,
|
||||
editingCourse: {},
|
||||
editingIndex: -1
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
});
|
||||
},
|
||||
|
||||
// 查看课表
|
||||
onViewSchedule() {
|
||||
wx.switchTab({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
}
|
||||
})
|
@ -0,0 +1,536 @@
|
||||
/**ocr-import.wxss**/
|
||||
.container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
/* 页面标题 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #1296db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 步骤指示器 */
|
||||
.step-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
background-color: white;
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
}
|
||||
|
||||
.step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #e5e5e5;
|
||||
color: #999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.step.active .step-number {
|
||||
background-color: #1296db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.step-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.step.active .step-text {
|
||||
color: #1296db;
|
||||
}
|
||||
|
||||
.step-line {
|
||||
flex: 1;
|
||||
height: 2rpx;
|
||||
background-color: #e5e5e5;
|
||||
margin: 0 20rpx;
|
||||
margin-bottom: 36rpx;
|
||||
}
|
||||
|
||||
.step-line.active {
|
||||
background-color: #1296db;
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 上传区域 */
|
||||
.upload-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
flex: 1;
|
||||
border: 2rpx dashed #ccc;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: white;
|
||||
min-height: 400rpx;
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.upload-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 60rpx;
|
||||
}
|
||||
|
||||
.upload-icon {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.upload-tip {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.image-actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.loading-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding: 60rpx;
|
||||
}
|
||||
|
||||
.loading-animation {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.loading-dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #1296db;
|
||||
animation: loading 1.4s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.loading-dot:nth-child(1) {
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.loading-dot:nth-child(2) {
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0%, 80%, 100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
40% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.loading-tip {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* OCR结果 */
|
||||
.ocr-result-section {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.result-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.result-count {
|
||||
font-size: 28rpx;
|
||||
color: #1296db;
|
||||
}
|
||||
|
||||
.course-list {
|
||||
flex: 1;
|
||||
background-color: white;
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.course-item {
|
||||
padding: 24rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.course-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.course-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.course-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.course-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
background-color: #1296db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.course-switch {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
.course-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.course-detail {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 空结果 */
|
||||
.empty-result {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
padding: 60rpx;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 32rpx;
|
||||
color: #999;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
font-size: 28rpx;
|
||||
color: #ccc;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 导入结果 */
|
||||
.import-result-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding: 60rpx;
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.import-summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.summary-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
font-size: 26rpx;
|
||||
color: #ff4757;
|
||||
}
|
||||
|
||||
/* 错误详情样式 */
|
||||
.error-details {
|
||||
width: 100%;
|
||||
background-color: #fff5f5;
|
||||
border: 1rpx solid #fecaca;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #dc2626;
|
||||
margin-bottom: 16rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.error-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.error-item {
|
||||
font-size: 26rpx;
|
||||
color: #991b1b;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 8rpx;
|
||||
padding-left: 20rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.error-item::before {
|
||||
content: "•";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.result-actions,
|
||||
.final-actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 30rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.action-btn.primary {
|
||||
background: linear-gradient(135deg, #1296db 0%, #0d7ec7 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.secondary {
|
||||
background-color: #f8f8f8;
|
||||
color: #1296db;
|
||||
border: 1rpx solid #1296db;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
/* 编辑弹窗样式 */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.edit-modal {
|
||||
background-color: white;
|
||||
border-radius: 16rpx;
|
||||
width: 90%;
|
||||
max-width: 600rpx;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 40rpx;
|
||||
color: #999;
|
||||
padding: 0;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
flex: 1;
|
||||
padding: 32rpx;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.form-group.half {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 12rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 20rpx;
|
||||
border: 1rpx solid #e0e0e0;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
border-color: #1296db;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.picker-display {
|
||||
padding: 20rpx;
|
||||
border: 1rpx solid #e0e0e0;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
background-color: #fafafa;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
padding: 32rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "个人中心",
|
||||
"navigationBarBackgroundColor": "#1296db",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
<!--profile.wxml-->
|
||||
<view class="container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<text class="page-title">个人中心</text>
|
||||
</view>
|
||||
|
||||
<!-- 用户信息区域 -->
|
||||
<view class="user-section">
|
||||
<view class="user-info">
|
||||
<image class="avatar" src="{{userInfo.avatarUrl || '/images/logo.png'}}" mode="aspectFill" />
|
||||
<view class="user-details">
|
||||
<text class="nickname">{{userInfo.nickName || '微信用户'}}</text>
|
||||
<text class="user-id">ID: {{userOpenid}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能菜单 -->
|
||||
<scroll-view class="menu-content" scroll-y="true">
|
||||
|
||||
<!-- 基础设置 -->
|
||||
<view class="menu-section">
|
||||
<text class="section-title">基础设置</text>
|
||||
<view class="menu-list">
|
||||
<view class="menu-item" bindtap="onAddCourse">
|
||||
<view class="menu-content">
|
||||
<text class="menu-title">添加课程</text>
|
||||
<text class="menu-desc">手动添加新的课程</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" bindtap="onOcrImport">
|
||||
<view class="menu-content">
|
||||
<text class="menu-title">OCR导入</text>
|
||||
<text class="menu-desc">拍照识别课表快速导入</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" bindtap="onSemesterSetting">
|
||||
<view class="menu-content">
|
||||
<text class="menu-title">学期设置</text>
|
||||
<text class="menu-desc">{{semesterConfig.name}} (第{{currentWeek}}周)</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 清空课程表 -->
|
||||
<view class="menu-section">
|
||||
<text class="section-title">数据管理</text>
|
||||
<view class="menu-list">
|
||||
<view class="menu-item" bindtap="onClearAllCourses">
|
||||
<view class="menu-content">
|
||||
<text class="menu-title danger">清空课程表</text>
|
||||
<text class="menu-desc">删除所有课程数据</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 关于我们 -->
|
||||
<view class="menu-section">
|
||||
<text class="section-title">关于</text>
|
||||
<view class="menu-list">
|
||||
<view class="menu-item" bindtap="onAbout">
|
||||
<view class="menu-content">
|
||||
<text class="menu-title">关于我们</text>
|
||||
<text class="menu-desc">版本信息和开发团队</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
</scroll-view>
|
||||
|
||||
<!-- 版本信息 -->
|
||||
<view class="footer">
|
||||
<text class="version-text">课表助手 v{{appVersion}}</text>
|
||||
<text class="copyright">© 2025 课表OCR识别小程序</text>
|
||||
</view>
|
||||
</view>
|
@ -0,0 +1,188 @@
|
||||
/**profile.wxss**/
|
||||
.container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
/* 页面标题 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #1296db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 用户信息区域 */
|
||||
.user-section {
|
||||
background: linear-gradient(135deg, #1296db 0%, #0d7ec7 100%);
|
||||
padding: 40rpx 30rpx;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
border: 4rpx solid rgba(255,255,255,0.3);
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
padding: 8rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.user-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.user-id {
|
||||
font-size: 26rpx;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 菜单内容 */
|
||||
.menu-content {
|
||||
flex: 1;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.menu-section {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 20rpx;
|
||||
padding-left: 10rpx;
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
background-color: white;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.menu-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.menu-item:active {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.menu-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.menu-title.danger {
|
||||
color: #ff4757;
|
||||
}
|
||||
|
||||
.menu-desc {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.menu-arrow {
|
||||
font-size: 28rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
/* 统计信息 */
|
||||
.stats-section {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: flex;
|
||||
background-color: white;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40rpx 20rpx;
|
||||
border-right: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.stat-item:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #1296db;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 底部信息 */
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
gap: 8rpx;
|
||||
background-color: white;
|
||||
border-top: 1rpx solid #e5e5e5;
|
||||
}
|
||||
|
||||
.version-text {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.copyright {
|
||||
font-size: 24rpx;
|
||||
color: #ccc;
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "miniprogram-ts-quickstart",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "",
|
||||
"dependencies": {
|
||||
},
|
||||
"devDependencies": {
|
||||
"miniprogram-api-typings": "^2.8.3-1"
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
{
|
||||
"description": "项目配置文件",
|
||||
"miniprogramRoot": "miniprogram/",
|
||||
"compileType": "miniprogram",
|
||||
"setting": {
|
||||
"useCompilerPlugins": [
|
||||
"typescript"
|
||||
],
|
||||
"babelSetting": {
|
||||
"ignore": [],
|
||||
"disablePlugins": [],
|
||||
"outputPath": ""
|
||||
},
|
||||
"coverView": false,
|
||||
"postcss": false,
|
||||
"minified": false,
|
||||
"enhance": false,
|
||||
"showShadowRootInWxmlPanel": false,
|
||||
"packNpmRelationList": [],
|
||||
"ignoreUploadUnusedFiles": true,
|
||||
"compileHotReLoad": false,
|
||||
"skylineRenderEnable": true
|
||||
},
|
||||
"simulatorType": "wechat",
|
||||
"simulatorPluginLibVersion": {},
|
||||
"condition": {},
|
||||
"srcMiniprogramRoot": "miniprogram/",
|
||||
"editorSetting": {
|
||||
"tabIndent": "insertSpaces",
|
||||
"tabSize": 2
|
||||
},
|
||||
"libVersion": "trial",
|
||||
"packOptions": {
|
||||
"ignore": [],
|
||||
"include": []
|
||||
},
|
||||
"appid": "wxd13571dd1337b69d"
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"module": "CommonJS",
|
||||
"target": "ES2020",
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitReturns": true,
|
||||
"alwaysStrict": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"strict": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"lib": ["ES2020"],
|
||||
"typeRoots": [
|
||||
"./typings"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
/// <reference path="./types/index.d.ts" />
|
||||
|
||||
interface IAppOption {
|
||||
globalData: {
|
||||
userInfo?: WechatMiniprogram.UserInfo,
|
||||
}
|
||||
userInfoReadyCallback?: WechatMiniprogram.GetUserInfoSuccessCallback,
|
||||
}
|
@ -0,0 +1 @@
|
||||
/// <reference path="./wx/index.d.ts" />
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,68 @@
|
||||
/*! *****************************************************************************
|
||||
Copyright (c) 2021 Tencent, Inc. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
***************************************************************************** */
|
||||
|
||||
declare namespace WechatMiniprogram.Behavior {
|
||||
type BehaviorIdentifier = string
|
||||
type Instance<
|
||||
TData extends DataOption,
|
||||
TProperty extends PropertyOption,
|
||||
TMethod extends MethodOption,
|
||||
TCustomInstanceProperty extends IAnyObject = Record<string, never>
|
||||
> = Component.Instance<TData, TProperty, TMethod, TCustomInstanceProperty>
|
||||
type TrivialInstance = Instance<IAnyObject, IAnyObject, IAnyObject>
|
||||
type TrivialOption = Options<IAnyObject, IAnyObject, IAnyObject>
|
||||
type Options<
|
||||
TData extends DataOption,
|
||||
TProperty extends PropertyOption,
|
||||
TMethod extends MethodOption,
|
||||
TCustomInstanceProperty extends IAnyObject = Record<string, never>
|
||||
> = Partial<Data<TData>> &
|
||||
Partial<Property<TProperty>> &
|
||||
Partial<Method<TMethod>> &
|
||||
Partial<OtherOption> &
|
||||
Partial<Lifetimes> &
|
||||
ThisType<Instance<TData, TProperty, TMethod, TCustomInstanceProperty>>
|
||||
interface Constructor {
|
||||
<
|
||||
TData extends DataOption,
|
||||
TProperty extends PropertyOption,
|
||||
TMethod extends MethodOption,
|
||||
TCustomInstanceProperty extends IAnyObject = Record<string, never>
|
||||
>(
|
||||
options: Options<TData, TProperty, TMethod, TCustomInstanceProperty>
|
||||
): BehaviorIdentifier
|
||||
}
|
||||
|
||||
type DataOption = Component.DataOption
|
||||
type PropertyOption = Component.PropertyOption
|
||||
type MethodOption = Component.MethodOption
|
||||
type Data<D extends DataOption> = Component.Data<D>
|
||||
type Property<P extends PropertyOption> = Component.Property<P>
|
||||
type Method<M extends MethodOption> = Component.Method<M>
|
||||
|
||||
type DefinitionFilter = Component.DefinitionFilter
|
||||
type Lifetimes = Component.Lifetimes
|
||||
|
||||
type OtherOption = Omit<Component.OtherOption, 'options'>
|
||||
}
|
||||
/** 注册一个 `behavior`,接受一个 `Object` 类型的参数。*/
|
||||
declare let Behavior: WechatMiniprogram.Behavior.Constructor
|
@ -0,0 +1,924 @@
|
||||
/*! *****************************************************************************
|
||||
Copyright (c) 2021 Tencent, Inc. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
***************************************************************************** */
|
||||
|
||||
interface IAPIError {
|
||||
errMsg: string
|
||||
}
|
||||
|
||||
interface IAPIParam<T = any> {
|
||||
config?: ICloudConfig
|
||||
success?: (res: T) => void
|
||||
fail?: (err: IAPIError) => void
|
||||
complete?: (val: T | IAPIError) => void
|
||||
}
|
||||
|
||||
interface IAPISuccessParam {
|
||||
errMsg: string
|
||||
}
|
||||
|
||||
type IAPICompleteParam = IAPISuccessParam | IAPIError
|
||||
|
||||
type IAPIFunction<T, P extends IAPIParam<T>> = (param?: P) => Promise<T>
|
||||
|
||||
interface IInitCloudConfig {
|
||||
env?:
|
||||
| string
|
||||
| {
|
||||
database?: string
|
||||
functions?: string
|
||||
storage?: string
|
||||
}
|
||||
traceUser?: boolean
|
||||
}
|
||||
|
||||
interface ICloudConfig {
|
||||
env?: string
|
||||
traceUser?: boolean
|
||||
}
|
||||
|
||||
interface IICloudAPI {
|
||||
init: (config?: IInitCloudConfig) => void
|
||||
[api: string]: AnyFunction | IAPIFunction<any, any>
|
||||
}
|
||||
|
||||
interface ICloudService {
|
||||
name: string
|
||||
|
||||
getAPIs: () => { [name: string]: IAPIFunction<any, any> }
|
||||
}
|
||||
|
||||
interface ICloudServices {
|
||||
[serviceName: string]: ICloudService
|
||||
}
|
||||
|
||||
interface ICloudMetaData {
|
||||
session_id: string
|
||||
}
|
||||
|
||||
declare class InternalSymbol {}
|
||||
|
||||
interface AnyObject {
|
||||
[x: string]: any
|
||||
}
|
||||
|
||||
type AnyArray = any[]
|
||||
|
||||
type AnyFunction = (...args: any[]) => any
|
||||
|
||||
/**
|
||||
* extend wx with cloud
|
||||
*/
|
||||
interface WxCloud {
|
||||
init: (config?: ICloudConfig) => void
|
||||
|
||||
callFunction(param: OQ<ICloud.CallFunctionParam>): void
|
||||
callFunction(
|
||||
param: RQ<ICloud.CallFunctionParam>
|
||||
): Promise<ICloud.CallFunctionResult>
|
||||
|
||||
uploadFile(param: OQ<ICloud.UploadFileParam>): WechatMiniprogram.UploadTask
|
||||
uploadFile(
|
||||
param: RQ<ICloud.UploadFileParam>
|
||||
): Promise<ICloud.UploadFileResult>
|
||||
|
||||
downloadFile(
|
||||
param: OQ<ICloud.DownloadFileParam>
|
||||
): WechatMiniprogram.DownloadTask
|
||||
downloadFile(
|
||||
param: RQ<ICloud.DownloadFileParam>
|
||||
): Promise<ICloud.DownloadFileResult>
|
||||
|
||||
getTempFileURL(param: OQ<ICloud.GetTempFileURLParam>): void
|
||||
getTempFileURL(
|
||||
param: RQ<ICloud.GetTempFileURLParam>
|
||||
): Promise<ICloud.GetTempFileURLResult>
|
||||
|
||||
deleteFile(param: OQ<ICloud.DeleteFileParam>): void
|
||||
deleteFile(
|
||||
param: RQ<ICloud.DeleteFileParam>
|
||||
): Promise<ICloud.DeleteFileResult>
|
||||
|
||||
database: (config?: ICloudConfig) => DB.Database
|
||||
|
||||
CloudID: ICloud.ICloudIDConstructor
|
||||
CDN: ICloud.ICDNConstructor
|
||||
}
|
||||
|
||||
declare namespace ICloud {
|
||||
interface ICloudAPIParam<T = any> extends IAPIParam<T> {
|
||||
config?: ICloudConfig
|
||||
}
|
||||
|
||||
// === API: callFunction ===
|
||||
type CallFunctionData = AnyObject
|
||||
|
||||
interface CallFunctionResult extends IAPISuccessParam {
|
||||
result: AnyObject | string | undefined
|
||||
}
|
||||
|
||||
interface CallFunctionParam extends ICloudAPIParam<CallFunctionResult> {
|
||||
name: string
|
||||
data?: CallFunctionData
|
||||
slow?: boolean
|
||||
}
|
||||
// === end ===
|
||||
|
||||
// === API: uploadFile ===
|
||||
interface UploadFileResult extends IAPISuccessParam {
|
||||
fileID: string
|
||||
statusCode: number
|
||||
}
|
||||
|
||||
interface UploadFileParam extends ICloudAPIParam<UploadFileResult> {
|
||||
cloudPath: string
|
||||
filePath: string
|
||||
header?: AnyObject
|
||||
}
|
||||
// === end ===
|
||||
|
||||
// === API: downloadFile ===
|
||||
interface DownloadFileResult extends IAPISuccessParam {
|
||||
tempFilePath: string
|
||||
statusCode: number
|
||||
}
|
||||
|
||||
interface DownloadFileParam extends ICloudAPIParam<DownloadFileResult> {
|
||||
fileID: string
|
||||
cloudPath?: string
|
||||
}
|
||||
// === end ===
|
||||
|
||||
// === API: getTempFileURL ===
|
||||
interface GetTempFileURLResult extends IAPISuccessParam {
|
||||
fileList: GetTempFileURLResultItem[]
|
||||
}
|
||||
|
||||
interface GetTempFileURLResultItem {
|
||||
fileID: string
|
||||
tempFileURL: string
|
||||
maxAge: number
|
||||
status: number
|
||||
errMsg: string
|
||||
}
|
||||
|
||||
interface GetTempFileURLParam extends ICloudAPIParam<GetTempFileURLResult> {
|
||||
fileList: string[]
|
||||
}
|
||||
// === end ===
|
||||
|
||||
// === API: deleteFile ===
|
||||
interface DeleteFileResult extends IAPISuccessParam {
|
||||
fileList: DeleteFileResultItem[]
|
||||
}
|
||||
|
||||
interface DeleteFileResultItem {
|
||||
fileID: string
|
||||
status: number
|
||||
errMsg: string
|
||||
}
|
||||
|
||||
interface DeleteFileParam extends ICloudAPIParam<DeleteFileResult> {
|
||||
fileList: string[]
|
||||
}
|
||||
// === end ===
|
||||
|
||||
// === API: CloudID ===
|
||||
abstract class CloudID {
|
||||
constructor(cloudID: string)
|
||||
}
|
||||
|
||||
interface ICloudIDConstructor {
|
||||
new (cloudId: string): CloudID
|
||||
(cloudId: string): CloudID
|
||||
}
|
||||
// === end ===
|
||||
|
||||
// === API: CDN ===
|
||||
abstract class CDN {
|
||||
target: string | ArrayBuffer | ICDNFilePathSpec
|
||||
constructor(target: string | ArrayBuffer | ICDNFilePathSpec)
|
||||
}
|
||||
|
||||
interface ICDNFilePathSpec {
|
||||
type: 'filePath'
|
||||
filePath: string
|
||||
}
|
||||
|
||||
interface ICDNConstructor {
|
||||
new (options: string | ArrayBuffer | ICDNFilePathSpec): CDN
|
||||
(options: string | ArrayBuffer | ICDNFilePathSpec): CDN
|
||||
}
|
||||
// === end ===
|
||||
}
|
||||
|
||||
// === Database ===
|
||||
declare namespace DB {
|
||||
/**
|
||||
* The class of all exposed cloud database instances
|
||||
*/
|
||||
class Database {
|
||||
readonly config: ICloudConfig
|
||||
readonly command: DatabaseCommand
|
||||
readonly Geo: IGeo
|
||||
readonly serverDate: () => ServerDate
|
||||
readonly RegExp: IRegExpConstructor
|
||||
|
||||
private constructor()
|
||||
|
||||
collection(collectionName: string): CollectionReference
|
||||
}
|
||||
|
||||
class CollectionReference extends Query {
|
||||
readonly collectionName: string
|
||||
|
||||
private constructor(name: string, database: Database)
|
||||
|
||||
doc(docId: string | number): DocumentReference
|
||||
|
||||
add(options: OQ<IAddDocumentOptions>): void
|
||||
add(options: RQ<IAddDocumentOptions>): Promise<IAddResult>
|
||||
}
|
||||
|
||||
class DocumentReference {
|
||||
private constructor(docId: string | number, database: Database)
|
||||
|
||||
field(object: Record<string, any>): this
|
||||
|
||||
get(options: OQ<IGetDocumentOptions>): void
|
||||
get(options?: RQ<IGetDocumentOptions>): Promise<IQuerySingleResult>
|
||||
|
||||
set(options: OQ<ISetSingleDocumentOptions>): void
|
||||
set(options?: RQ<ISetSingleDocumentOptions>): Promise<ISetResult>
|
||||
|
||||
update(options: OQ<IUpdateSingleDocumentOptions>): void
|
||||
update(
|
||||
options?: RQ<IUpdateSingleDocumentOptions>
|
||||
): Promise<IUpdateResult>
|
||||
|
||||
remove(options: OQ<IRemoveSingleDocumentOptions>): void
|
||||
remove(
|
||||
options?: RQ<IRemoveSingleDocumentOptions>
|
||||
): Promise<IRemoveResult>
|
||||
|
||||
watch(options: IWatchOptions): RealtimeListener
|
||||
}
|
||||
|
||||
class RealtimeListener {
|
||||
// "And Now His Watch Is Ended"
|
||||
close: () => Promise<void>
|
||||
}
|
||||
|
||||
class Query {
|
||||
where(condition: IQueryCondition): Query
|
||||
|
||||
orderBy(fieldPath: string, order: string): Query
|
||||
|
||||
limit(max: number): Query
|
||||
|
||||
skip(offset: number): Query
|
||||
|
||||
field(object: Record<string, any>): Query
|
||||
|
||||
get(options: OQ<IGetDocumentOptions>): void
|
||||
get(options?: RQ<IGetDocumentOptions>): Promise<IQueryResult>
|
||||
|
||||
count(options: OQ<ICountDocumentOptions>): void
|
||||
count(options?: RQ<ICountDocumentOptions>): Promise<ICountResult>
|
||||
|
||||
watch(options: IWatchOptions): RealtimeListener
|
||||
}
|
||||
|
||||
interface DatabaseCommand {
|
||||
eq(val: any): DatabaseQueryCommand
|
||||
neq(val: any): DatabaseQueryCommand
|
||||
gt(val: any): DatabaseQueryCommand
|
||||
gte(val: any): DatabaseQueryCommand
|
||||
lt(val: any): DatabaseQueryCommand
|
||||
lte(val: any): DatabaseQueryCommand
|
||||
in(val: any[]): DatabaseQueryCommand
|
||||
nin(val: any[]): DatabaseQueryCommand
|
||||
|
||||
geoNear(options: IGeoNearCommandOptions): DatabaseQueryCommand
|
||||
geoWithin(options: IGeoWithinCommandOptions): DatabaseQueryCommand
|
||||
geoIntersects(
|
||||
options: IGeoIntersectsCommandOptions
|
||||
): DatabaseQueryCommand
|
||||
|
||||
and(
|
||||
...expressions: Array<DatabaseLogicCommand | IQueryCondition>
|
||||
): DatabaseLogicCommand
|
||||
or(
|
||||
...expressions: Array<DatabaseLogicCommand | IQueryCondition>
|
||||
): DatabaseLogicCommand
|
||||
nor(
|
||||
...expressions: Array<DatabaseLogicCommand | IQueryCondition>
|
||||
): DatabaseLogicCommand
|
||||
not(expression: DatabaseLogicCommand): DatabaseLogicCommand
|
||||
|
||||
exists(val: boolean): DatabaseQueryCommand
|
||||
|
||||
mod(divisor: number, remainder: number): DatabaseQueryCommand
|
||||
|
||||
all(val: any[]): DatabaseQueryCommand
|
||||
elemMatch(val: any): DatabaseQueryCommand
|
||||
size(val: number): DatabaseQueryCommand
|
||||
|
||||
set(val: any): DatabaseUpdateCommand
|
||||
remove(): DatabaseUpdateCommand
|
||||
inc(val: number): DatabaseUpdateCommand
|
||||
mul(val: number): DatabaseUpdateCommand
|
||||
min(val: number): DatabaseUpdateCommand
|
||||
max(val: number): DatabaseUpdateCommand
|
||||
rename(val: string): DatabaseUpdateCommand
|
||||
bit(val: number): DatabaseUpdateCommand
|
||||
|
||||
push(...values: any[]): DatabaseUpdateCommand
|
||||
pop(): DatabaseUpdateCommand
|
||||
shift(): DatabaseUpdateCommand
|
||||
unshift(...values: any[]): DatabaseUpdateCommand
|
||||
addToSet(val: any): DatabaseUpdateCommand
|
||||
pull(val: any): DatabaseUpdateCommand
|
||||
pullAll(val: any): DatabaseUpdateCommand
|
||||
|
||||
project: {
|
||||
slice(val: number | [number, number]): DatabaseProjectionCommand
|
||||
}
|
||||
|
||||
aggregate: {
|
||||
__safe_props__?: Set<string>
|
||||
|
||||
abs(val: any): DatabaseAggregateCommand
|
||||
add(val: any): DatabaseAggregateCommand
|
||||
addToSet(val: any): DatabaseAggregateCommand
|
||||
allElementsTrue(val: any): DatabaseAggregateCommand
|
||||
and(val: any): DatabaseAggregateCommand
|
||||
anyElementTrue(val: any): DatabaseAggregateCommand
|
||||
arrayElemAt(val: any): DatabaseAggregateCommand
|
||||
arrayToObject(val: any): DatabaseAggregateCommand
|
||||
avg(val: any): DatabaseAggregateCommand
|
||||
ceil(val: any): DatabaseAggregateCommand
|
||||
cmp(val: any): DatabaseAggregateCommand
|
||||
concat(val: any): DatabaseAggregateCommand
|
||||
concatArrays(val: any): DatabaseAggregateCommand
|
||||
cond(val: any): DatabaseAggregateCommand
|
||||
convert(val: any): DatabaseAggregateCommand
|
||||
dateFromParts(val: any): DatabaseAggregateCommand
|
||||
dateToParts(val: any): DatabaseAggregateCommand
|
||||
dateFromString(val: any): DatabaseAggregateCommand
|
||||
dateToString(val: any): DatabaseAggregateCommand
|
||||
dayOfMonth(val: any): DatabaseAggregateCommand
|
||||
dayOfWeek(val: any): DatabaseAggregateCommand
|
||||
dayOfYear(val: any): DatabaseAggregateCommand
|
||||
divide(val: any): DatabaseAggregateCommand
|
||||
eq(val: any): DatabaseAggregateCommand
|
||||
exp(val: any): DatabaseAggregateCommand
|
||||
filter(val: any): DatabaseAggregateCommand
|
||||
first(val: any): DatabaseAggregateCommand
|
||||
floor(val: any): DatabaseAggregateCommand
|
||||
gt(val: any): DatabaseAggregateCommand
|
||||
gte(val: any): DatabaseAggregateCommand
|
||||
hour(val: any): DatabaseAggregateCommand
|
||||
ifNull(val: any): DatabaseAggregateCommand
|
||||
in(val: any): DatabaseAggregateCommand
|
||||
indexOfArray(val: any): DatabaseAggregateCommand
|
||||
indexOfBytes(val: any): DatabaseAggregateCommand
|
||||
indexOfCP(val: any): DatabaseAggregateCommand
|
||||
isArray(val: any): DatabaseAggregateCommand
|
||||
isoDayOfWeek(val: any): DatabaseAggregateCommand
|
||||
isoWeek(val: any): DatabaseAggregateCommand
|
||||
isoWeekYear(val: any): DatabaseAggregateCommand
|
||||
last(val: any): DatabaseAggregateCommand
|
||||
let(val: any): DatabaseAggregateCommand
|
||||
literal(val: any): DatabaseAggregateCommand
|
||||
ln(val: any): DatabaseAggregateCommand
|
||||
log(val: any): DatabaseAggregateCommand
|
||||
log10(val: any): DatabaseAggregateCommand
|
||||
lt(val: any): DatabaseAggregateCommand
|
||||
lte(val: any): DatabaseAggregateCommand
|
||||
ltrim(val: any): DatabaseAggregateCommand
|
||||
map(val: any): DatabaseAggregateCommand
|
||||
max(val: any): DatabaseAggregateCommand
|
||||
mergeObjects(val: any): DatabaseAggregateCommand
|
||||
meta(val: any): DatabaseAggregateCommand
|
||||
min(val: any): DatabaseAggregateCommand
|
||||
millisecond(val: any): DatabaseAggregateCommand
|
||||
minute(val: any): DatabaseAggregateCommand
|
||||
mod(val: any): DatabaseAggregateCommand
|
||||
month(val: any): DatabaseAggregateCommand
|
||||
multiply(val: any): DatabaseAggregateCommand
|
||||
neq(val: any): DatabaseAggregateCommand
|
||||
not(val: any): DatabaseAggregateCommand
|
||||
objectToArray(val: any): DatabaseAggregateCommand
|
||||
or(val: any): DatabaseAggregateCommand
|
||||
pow(val: any): DatabaseAggregateCommand
|
||||
push(val: any): DatabaseAggregateCommand
|
||||
range(val: any): DatabaseAggregateCommand
|
||||
reduce(val: any): DatabaseAggregateCommand
|
||||
reverseArray(val: any): DatabaseAggregateCommand
|
||||
rtrim(val: any): DatabaseAggregateCommand
|
||||
second(val: any): DatabaseAggregateCommand
|
||||
setDifference(val: any): DatabaseAggregateCommand
|
||||
setEquals(val: any): DatabaseAggregateCommand
|
||||
setIntersection(val: any): DatabaseAggregateCommand
|
||||
setIsSubset(val: any): DatabaseAggregateCommand
|
||||
setUnion(val: any): DatabaseAggregateCommand
|
||||
size(val: any): DatabaseAggregateCommand
|
||||
slice(val: any): DatabaseAggregateCommand
|
||||
split(val: any): DatabaseAggregateCommand
|
||||
sqrt(val: any): DatabaseAggregateCommand
|
||||
stdDevPop(val: any): DatabaseAggregateCommand
|
||||
stdDevSamp(val: any): DatabaseAggregateCommand
|
||||
strcasecmp(val: any): DatabaseAggregateCommand
|
||||
strLenBytes(val: any): DatabaseAggregateCommand
|
||||
strLenCP(val: any): DatabaseAggregateCommand
|
||||
substr(val: any): DatabaseAggregateCommand
|
||||
substrBytes(val: any): DatabaseAggregateCommand
|
||||
substrCP(val: any): DatabaseAggregateCommand
|
||||
subtract(val: any): DatabaseAggregateCommand
|
||||
sum(val: any): DatabaseAggregateCommand
|
||||
switch(val: any): DatabaseAggregateCommand
|
||||
toBool(val: any): DatabaseAggregateCommand
|
||||
toDate(val: any): DatabaseAggregateCommand
|
||||
toDecimal(val: any): DatabaseAggregateCommand
|
||||
toDouble(val: any): DatabaseAggregateCommand
|
||||
toInt(val: any): DatabaseAggregateCommand
|
||||
toLong(val: any): DatabaseAggregateCommand
|
||||
toObjectId(val: any): DatabaseAggregateCommand
|
||||
toString(val: any): DatabaseAggregateCommand
|
||||
toLower(val: any): DatabaseAggregateCommand
|
||||
toUpper(val: any): DatabaseAggregateCommand
|
||||
trim(val: any): DatabaseAggregateCommand
|
||||
trunc(val: any): DatabaseAggregateCommand
|
||||
type(val: any): DatabaseAggregateCommand
|
||||
week(val: any): DatabaseAggregateCommand
|
||||
year(val: any): DatabaseAggregateCommand
|
||||
zip(val: any): DatabaseAggregateCommand
|
||||
}
|
||||
}
|
||||
|
||||
class DatabaseAggregateCommand {}
|
||||
|
||||
enum LOGIC_COMMANDS_LITERAL {
|
||||
AND = 'and',
|
||||
OR = 'or',
|
||||
NOT = 'not',
|
||||
NOR = 'nor'
|
||||
}
|
||||
|
||||
class DatabaseLogicCommand {
|
||||
and(...expressions: DatabaseLogicCommand[]): DatabaseLogicCommand
|
||||
or(...expressions: DatabaseLogicCommand[]): DatabaseLogicCommand
|
||||
nor(...expressions: DatabaseLogicCommand[]): DatabaseLogicCommand
|
||||
not(expression: DatabaseLogicCommand): DatabaseLogicCommand
|
||||
}
|
||||
|
||||
enum QUERY_COMMANDS_LITERAL {
|
||||
// comparison
|
||||
EQ = 'eq',
|
||||
NEQ = 'neq',
|
||||
GT = 'gt',
|
||||
GTE = 'gte',
|
||||
LT = 'lt',
|
||||
LTE = 'lte',
|
||||
IN = 'in',
|
||||
NIN = 'nin',
|
||||
// geo
|
||||
GEO_NEAR = 'geoNear',
|
||||
GEO_WITHIN = 'geoWithin',
|
||||
GEO_INTERSECTS = 'geoIntersects',
|
||||
// element
|
||||
EXISTS = 'exists',
|
||||
// evaluation
|
||||
MOD = 'mod',
|
||||
// array
|
||||
ALL = 'all',
|
||||
ELEM_MATCH = 'elemMatch',
|
||||
SIZE = 'size'
|
||||
}
|
||||
|
||||
class DatabaseQueryCommand extends DatabaseLogicCommand {
|
||||
eq(val: any): DatabaseLogicCommand
|
||||
neq(val: any): DatabaseLogicCommand
|
||||
gt(val: any): DatabaseLogicCommand
|
||||
gte(val: any): DatabaseLogicCommand
|
||||
lt(val: any): DatabaseLogicCommand
|
||||
lte(val: any): DatabaseLogicCommand
|
||||
in(val: any[]): DatabaseLogicCommand
|
||||
nin(val: any[]): DatabaseLogicCommand
|
||||
|
||||
exists(val: boolean): DatabaseLogicCommand
|
||||
|
||||
mod(divisor: number, remainder: number): DatabaseLogicCommand
|
||||
|
||||
all(val: any[]): DatabaseLogicCommand
|
||||
elemMatch(val: any): DatabaseLogicCommand
|
||||
size(val: number): DatabaseLogicCommand
|
||||
|
||||
geoNear(options: IGeoNearCommandOptions): DatabaseLogicCommand
|
||||
geoWithin(options: IGeoWithinCommandOptions): DatabaseLogicCommand
|
||||
geoIntersects(
|
||||
options: IGeoIntersectsCommandOptions
|
||||
): DatabaseLogicCommand
|
||||
}
|
||||
|
||||
enum PROJECTION_COMMANDS_LITERAL {
|
||||
SLICE = 'slice'
|
||||
}
|
||||
|
||||
class DatabaseProjectionCommand {}
|
||||
|
||||
enum UPDATE_COMMANDS_LITERAL {
|
||||
// field
|
||||
SET = 'set',
|
||||
REMOVE = 'remove',
|
||||
INC = 'inc',
|
||||
MUL = 'mul',
|
||||
MIN = 'min',
|
||||
MAX = 'max',
|
||||
RENAME = 'rename',
|
||||
// bitwise
|
||||
BIT = 'bit',
|
||||
// array
|
||||
PUSH = 'push',
|
||||
POP = 'pop',
|
||||
SHIFT = 'shift',
|
||||
UNSHIFT = 'unshift',
|
||||
ADD_TO_SET = 'addToSet',
|
||||
PULL = 'pull',
|
||||
PULL_ALL = 'pullAll'
|
||||
}
|
||||
|
||||
class DatabaseUpdateCommand {}
|
||||
|
||||
class Batch {}
|
||||
|
||||
/**
|
||||
* A contract that all API provider must adhere to
|
||||
*/
|
||||
class APIBaseContract<
|
||||
PromiseReturn,
|
||||
CallbackReturn,
|
||||
Param extends IAPIParam,
|
||||
Context = any
|
||||
> {
|
||||
getContext(param: Param): Context
|
||||
|
||||
/**
|
||||
* In case of callback-style invocation, this function will be called
|
||||
*/
|
||||
getCallbackReturn(param: Param, context: Context): CallbackReturn
|
||||
|
||||
getFinalParam<T extends Param>(param: Param, context: Context): T
|
||||
|
||||
run<T extends Param>(param: T): Promise<PromiseReturn>
|
||||
}
|
||||
|
||||
interface IGeoPointConstructor {
|
||||
new (longitude: number, latitide: number): GeoPoint
|
||||
new (geojson: IGeoJSONPoint): GeoPoint
|
||||
(longitude: number, latitide: number): GeoPoint
|
||||
(geojson: IGeoJSONPoint): GeoPoint
|
||||
}
|
||||
|
||||
interface IGeoMultiPointConstructor {
|
||||
new (points: GeoPoint[] | IGeoJSONMultiPoint): GeoMultiPoint
|
||||
(points: GeoPoint[] | IGeoJSONMultiPoint): GeoMultiPoint
|
||||
}
|
||||
|
||||
interface IGeoLineStringConstructor {
|
||||
new (points: GeoPoint[] | IGeoJSONLineString): GeoLineString
|
||||
(points: GeoPoint[] | IGeoJSONLineString): GeoLineString
|
||||
}
|
||||
|
||||
interface IGeoMultiLineStringConstructor {
|
||||
new (
|
||||
lineStrings: GeoLineString[] | IGeoJSONMultiLineString
|
||||
): GeoMultiLineString
|
||||
(
|
||||
lineStrings: GeoLineString[] | IGeoJSONMultiLineString
|
||||
): GeoMultiLineString
|
||||
}
|
||||
|
||||
interface IGeoPolygonConstructor {
|
||||
new (lineStrings: GeoLineString[] | IGeoJSONPolygon): GeoPolygon
|
||||
(lineStrings: GeoLineString[] | IGeoJSONPolygon): GeoPolygon
|
||||
}
|
||||
|
||||
interface IGeoMultiPolygonConstructor {
|
||||
new (polygons: GeoPolygon[] | IGeoJSONMultiPolygon): GeoMultiPolygon
|
||||
(polygons: GeoPolygon[] | IGeoJSONMultiPolygon): GeoMultiPolygon
|
||||
}
|
||||
|
||||
interface IGeo {
|
||||
Point: IGeoPointConstructor
|
||||
MultiPoint: IGeoMultiPointConstructor
|
||||
LineString: IGeoLineStringConstructor
|
||||
MultiLineString: IGeoMultiLineStringConstructor
|
||||
Polygon: IGeoPolygonConstructor
|
||||
MultiPolygon: IGeoMultiPolygonConstructor
|
||||
}
|
||||
|
||||
interface IGeoJSONPoint {
|
||||
type: 'Point'
|
||||
coordinates: [number, number]
|
||||
}
|
||||
|
||||
interface IGeoJSONMultiPoint {
|
||||
type: 'MultiPoint'
|
||||
coordinates: Array<[number, number]>
|
||||
}
|
||||
|
||||
interface IGeoJSONLineString {
|
||||
type: 'LineString'
|
||||
coordinates: Array<[number, number]>
|
||||
}
|
||||
|
||||
interface IGeoJSONMultiLineString {
|
||||
type: 'MultiLineString'
|
||||
coordinates: Array<Array<[number, number]>>
|
||||
}
|
||||
|
||||
interface IGeoJSONPolygon {
|
||||
type: 'Polygon'
|
||||
coordinates: Array<Array<[number, number]>>
|
||||
}
|
||||
|
||||
interface IGeoJSONMultiPolygon {
|
||||
type: 'MultiPolygon'
|
||||
coordinates: Array<Array<Array<[number, number]>>>
|
||||
}
|
||||
|
||||
type IGeoJSONObject =
|
||||
| IGeoJSONPoint
|
||||
| IGeoJSONMultiPoint
|
||||
| IGeoJSONLineString
|
||||
| IGeoJSONMultiLineString
|
||||
| IGeoJSONPolygon
|
||||
| IGeoJSONMultiPolygon
|
||||
|
||||
abstract class GeoPoint {
|
||||
longitude: number
|
||||
latitude: number
|
||||
|
||||
constructor(longitude: number, latitude: number)
|
||||
|
||||
toJSON(): Record<string, any>
|
||||
toString(): string
|
||||
}
|
||||
|
||||
abstract class GeoMultiPoint {
|
||||
points: GeoPoint[]
|
||||
|
||||
constructor(points: GeoPoint[])
|
||||
|
||||
toJSON(): IGeoJSONMultiPoint
|
||||
toString(): string
|
||||
}
|
||||
|
||||
abstract class GeoLineString {
|
||||
points: GeoPoint[]
|
||||
|
||||
constructor(points: GeoPoint[])
|
||||
|
||||
toJSON(): IGeoJSONLineString
|
||||
toString(): string
|
||||
}
|
||||
|
||||
abstract class GeoMultiLineString {
|
||||
lines: GeoLineString[]
|
||||
|
||||
constructor(lines: GeoLineString[])
|
||||
|
||||
toJSON(): IGeoJSONMultiLineString
|
||||
toString(): string
|
||||
}
|
||||
|
||||
abstract class GeoPolygon {
|
||||
lines: GeoLineString[]
|
||||
|
||||
constructor(lines: GeoLineString[])
|
||||
|
||||
toJSON(): IGeoJSONPolygon
|
||||
toString(): string
|
||||
}
|
||||
|
||||
abstract class GeoMultiPolygon {
|
||||
polygons: GeoPolygon[]
|
||||
|
||||
constructor(polygons: GeoPolygon[])
|
||||
|
||||
toJSON(): IGeoJSONMultiPolygon
|
||||
toString(): string
|
||||
}
|
||||
|
||||
type GeoInstance =
|
||||
| GeoPoint
|
||||
| GeoMultiPoint
|
||||
| GeoLineString
|
||||
| GeoMultiLineString
|
||||
| GeoPolygon
|
||||
| GeoMultiPolygon
|
||||
|
||||
interface IGeoNearCommandOptions {
|
||||
geometry: GeoPoint
|
||||
maxDistance?: number
|
||||
minDistance?: number
|
||||
}
|
||||
|
||||
interface IGeoWithinCommandOptions {
|
||||
geometry: GeoPolygon | GeoMultiPolygon
|
||||
}
|
||||
|
||||
interface IGeoIntersectsCommandOptions {
|
||||
geometry:
|
||||
| GeoPoint
|
||||
| GeoMultiPoint
|
||||
| GeoLineString
|
||||
| GeoMultiLineString
|
||||
| GeoPolygon
|
||||
| GeoMultiPolygon
|
||||
}
|
||||
|
||||
interface IServerDateOptions {
|
||||
offset: number
|
||||
}
|
||||
|
||||
abstract class ServerDate {
|
||||
readonly options: IServerDateOptions
|
||||
constructor(options?: IServerDateOptions)
|
||||
}
|
||||
|
||||
interface IRegExpOptions {
|
||||
regexp: string
|
||||
options?: string
|
||||
}
|
||||
|
||||
interface IRegExpConstructor {
|
||||
new (options: IRegExpOptions): RegExp
|
||||
(options: IRegExpOptions): RegExp
|
||||
}
|
||||
|
||||
abstract class RegExp {
|
||||
readonly regexp: string
|
||||
readonly options: string
|
||||
constructor(options: IRegExpOptions)
|
||||
}
|
||||
|
||||
type DocumentId = string | number
|
||||
|
||||
interface IDocumentData {
|
||||
_id?: DocumentId
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
type IDBAPIParam = IAPIParam
|
||||
|
||||
interface IAddDocumentOptions extends IDBAPIParam {
|
||||
data: IDocumentData
|
||||
}
|
||||
|
||||
type IGetDocumentOptions = IDBAPIParam
|
||||
|
||||
type ICountDocumentOptions = IDBAPIParam
|
||||
|
||||
interface IUpdateDocumentOptions extends IDBAPIParam {
|
||||
data: IUpdateCondition
|
||||
}
|
||||
|
||||
interface IUpdateSingleDocumentOptions extends IDBAPIParam {
|
||||
data: IUpdateCondition
|
||||
}
|
||||
|
||||
interface ISetDocumentOptions extends IDBAPIParam {
|
||||
data: IUpdateCondition
|
||||
}
|
||||
|
||||
interface ISetSingleDocumentOptions extends IDBAPIParam {
|
||||
data: IUpdateCondition
|
||||
}
|
||||
|
||||
interface IRemoveDocumentOptions extends IDBAPIParam {
|
||||
query: IQueryCondition
|
||||
}
|
||||
|
||||
type IRemoveSingleDocumentOptions = IDBAPIParam
|
||||
|
||||
interface IWatchOptions {
|
||||
// server realtime data init & change event
|
||||
onChange: (snapshot: ISnapshot) => void
|
||||
// error while connecting / listening
|
||||
onError: (error: any) => void
|
||||
}
|
||||
|
||||
interface ISnapshot {
|
||||
id: number
|
||||
docChanges: ISingleDBEvent[]
|
||||
docs: Record<string, any>
|
||||
type?: SnapshotType
|
||||
}
|
||||
|
||||
type SnapshotType = 'init'
|
||||
|
||||
interface ISingleDBEvent {
|
||||
id: number
|
||||
dataType: DataType
|
||||
queueType: QueueType
|
||||
docId: string
|
||||
doc: Record<string, any>
|
||||
updatedFields?: Record<string, any>
|
||||
removedFields?: string[]
|
||||
}
|
||||
|
||||
type DataType = 'init' | 'update' | 'replace' | 'add' | 'remove' | 'limit'
|
||||
|
||||
type QueueType = 'init' | 'enqueue' | 'dequeue' | 'update'
|
||||
|
||||
interface IQueryCondition {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
type IStringQueryCondition = string
|
||||
|
||||
interface IQueryResult extends IAPISuccessParam {
|
||||
data: IDocumentData[]
|
||||
}
|
||||
|
||||
interface IQuerySingleResult extends IAPISuccessParam {
|
||||
data: IDocumentData
|
||||
}
|
||||
|
||||
interface IUpdateCondition {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
type IStringUpdateCondition = string
|
||||
|
||||
interface IAddResult extends IAPISuccessParam {
|
||||
_id: DocumentId
|
||||
}
|
||||
|
||||
interface IUpdateResult extends IAPISuccessParam {
|
||||
stats: {
|
||||
updated: number
|
||||
// created: number,
|
||||
}
|
||||
}
|
||||
|
||||
interface ISetResult extends IAPISuccessParam {
|
||||
_id: DocumentId
|
||||
stats: {
|
||||
updated: number
|
||||
created: number
|
||||
}
|
||||
}
|
||||
|
||||
interface IRemoveResult extends IAPISuccessParam {
|
||||
stats: {
|
||||
removed: number
|
||||
}
|
||||
}
|
||||
|
||||
interface ICountResult extends IAPISuccessParam {
|
||||
total: number
|
||||
}
|
||||
}
|
||||
|
||||
type Optional<T> = { [K in keyof T]+?: T[K] }
|
||||
|
||||
type OQ<
|
||||
T extends Optional<
|
||||
Record<'complete' | 'success' | 'fail', (...args: any[]) => any>
|
||||
>
|
||||
> =
|
||||
| (RQ<T> & Required<Pick<T, 'success'>>)
|
||||
| (RQ<T> & Required<Pick<T, 'fail'>>)
|
||||
| (RQ<T> & Required<Pick<T, 'complete'>>)
|
||||
| (RQ<T> & Required<Pick<T, 'success' | 'fail'>>)
|
||||
| (RQ<T> & Required<Pick<T, 'success' | 'complete'>>)
|
||||
| (RQ<T> & Required<Pick<T, 'fail' | 'complete'>>)
|
||||
| (RQ<T> & Required<Pick<T, 'fail' | 'complete' | 'success'>>)
|
||||
|
||||
type RQ<
|
||||
T extends Optional<
|
||||
Record<'complete' | 'success' | 'fail', (...args: any[]) => any>
|
||||
>
|
||||
> = Pick<T, Exclude<keyof T, 'complete' | 'success' | 'fail'>>
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,47 @@
|
||||
# 底部导航功能说明
|
||||
|
||||
## ✅ 已完成的功能
|
||||
|
||||
### 1. 底部导航栏 (TabBar)
|
||||
- **首页**:显示课程表,查看今日课程
|
||||
- **个人中心**:管理课程和应用设置
|
||||
|
||||
### 2. 个人中心功能
|
||||
按照需求文档简化版本实现:
|
||||
|
||||
#### 基础设置
|
||||
- ✅ **添加课程**:跳转到手动添加课程页面
|
||||
- ✅ **OCR导入**:跳转到拍照识别导入页面
|
||||
|
||||
#### 清空课程表
|
||||
- ✅ **清空课程表**:删除所有课程数据(带确认提示)
|
||||
|
||||
#### 关于我们
|
||||
- ✅ **关于我们**:显示版本信息和开发团队
|
||||
|
||||
## 🎯 核心功能流程
|
||||
|
||||
### 用户使用流程:
|
||||
1. **首页** → 查看课程表
|
||||
2. **个人中心** → **OCR导入** → 拍照识别课表
|
||||
3. **个人中心** → **添加课程** → 手动添加课程
|
||||
4. **个人中心** → **清空课程表** → 重新开始
|
||||
|
||||
## 📱 界面特点
|
||||
|
||||
- **简洁设计**:移除了复杂的图标,使用纯文字导航
|
||||
- **核心功能**:专注于课表管理的核心需求
|
||||
- **用户友好**:清晰的导航和操作流程
|
||||
|
||||
## 🔧 技术实现
|
||||
|
||||
- 使用微信小程序原生 TabBar
|
||||
- 响应式布局适配不同屏幕
|
||||
- 数据同步:首页和个人中心数据实时同步
|
||||
|
||||
## 📝 使用说明
|
||||
|
||||
1. 打开小程序,默认进入**首页**
|
||||
2. 点击底部**个人中心**切换到设置页面
|
||||
3. 在个人中心可以进行课程管理操作
|
||||
4. 操作完成后自动返回首页查看结果
|
Loading…
Reference in new issue