nonvioce 6 месяцев назад
Родитель
Сommit
d96ed4c166
4 измененных файлов с 1119 добавлено и 0 удалено
  1. 733 0
      seafile.css
  2. 49 0
      txt文本提取数据.py
  3. 260 0
      文件分割器.py
  4. 77 0
      自动整理Downloads文件夹.py

+ 733 - 0
seafile.css

@@ -0,0 +1,733 @@
+/* =============================================
+   Phycat 主题 for Seafile (亮色模式 · Sakura 风格)
+   基于 Obsidian Phycat 主题移植,完全重写 Seafile 界面
+   ============================================= */
+
+:root {
+  --primary-color: #ff7096;
+  --secondary-color: #bd93f9;
+  --bg-color: #fff7fa;
+  --light-deep: #e91e63;
+  --light-light: #ffb7cd;
+  --light-lighter: #ffe3ec;
+  --light-pale: #fff0f5;
+  --text-color: #1c1719;
+  --text-color-secondary: #3f3a3b;
+  --link-hover-color: #d81b60;
+  --select-text-bg-color: rgba(255, 112, 150, 0.3);
+  --glow-color: rgba(255, 112, 150, 0.5);
+  --border-color: color-mix(in srgb, var(--primary-color), transparent 60%);
+  --h2-bg-gradient: linear-gradient(to right, var(--light-light), var(--primary-color), var(--light-light));
+  --text-bold: var(--light-deep);
+  --code-normal: #546e7a;
+  --code-string: #2e7d32;
+  --code-comment: #b0bec5;
+  --code-keyword: #d81b60;
+  --code-function: #8e24aa;
+  --code-value: #f57c00;
+  --code-tag: #d81b60;
+  --code-operator: #8e24aa;
+  --code-property: #00897b;
+  --code-punctuation: #78909c;
+  --code-block-bg: #fff4f8;
+  --code-block-header-bg: color-mix(in srgb, var(--primary-color), transparent 92%);
+  --blockquote-bg: #fff0f5;
+  --table-th-bg: #ffe3ec;
+  --table-border: #ffd9e2;
+  --checkbox-size: 1.2rem;
+  --checkmark-color: #ffffff;
+  --file-line-width: 900px;
+  --letter-spacing: 0.5px;
+  --line-height-customize: 1.7;
+  --code-line-height: 1.5;
+  --blockquote-line-height: 1.6;
+  --table-line-height: 1.6;
+  --heading-spacing: 1.2em;
+  --h1-size: 2.2em;
+  --h2-size: 1.8em;
+  --h3-size: 1.5em;
+  --h4-size: 1.3em;
+  --h5-size: 1.2em;
+  --h6-size: 1.2em;
+  --radius-small: 8px;
+  --radius-medium: 12px;
+  --radius-large: 20px;
+}
+
+/* ========== 全局基础样式 ========== */
+* {
+  border-radius: var(--radius-medium) !important;
+  box-sizing: border-box;
+}
+body {
+  background-color: var(--bg-color) !important;
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "LXGW WenKai", "霞鹜文楷", Helvetica, Arial, sans-serif !important;
+  color: var(--text-color) !important;
+  line-height: var(--line-height-customize) !important;
+  letter-spacing: var(--letter-spacing) !important;
+}
+
+/* ========== 导航栏 ========== */
+.navbar,
+.navbar-default {
+  background-color: var(--primary-color) !important;
+  border: none !important;
+  box-shadow: 0 4px 12px color-mix(in srgb, var(--primary-color), transparent 80%) !important;
+}
+.navbar-default .navbar-brand,
+.navbar-default .navbar-nav > li > a {
+  color: white !important;
+  font-weight: 500 !important;
+  transition: all 0.2s ease;
+}
+.navbar-default .navbar-nav > li > a:hover,
+.navbar-default .navbar-nav > .open > a,
+.navbar-default .navbar-nav > .open > a:hover,
+.navbar-default .navbar-nav > .open > a:focus {
+  background-color: color-mix(in srgb, var(--primary-color), black 10%) !important;
+  color: white !important;
+}
+.navbar-default .navbar-toggle {
+  border-color: rgba(255, 255, 255, 0.5) !important;
+}
+.navbar-default .navbar-toggle .icon-bar {
+  background-color: white !important;
+}
+
+/* ========== 按钮 ========== */
+.btn,
+.btn-primary,
+.btn-default {
+  border: none !important;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05) !important;
+  font-weight: 500 !important;
+  transition: all 0.2s ease !important;
+  border-radius: var(--radius-medium) !important;
+}
+.btn-primary {
+  background-color: var(--primary-color) !important;
+  color: white !important;
+}
+.btn-primary:hover,
+.btn-primary:focus {
+  background-color: var(--link-hover-color) !important;
+  box-shadow: 0 4px 12px var(--glow-color) !important;
+  transform: translateY(-2px);
+}
+.btn-primary:active {
+  transform: scale(0.96) !important;
+}
+.btn-default {
+  background-color: white !important;
+  color: var(--text-color) !important;
+  border: 1px solid var(--border-color) !important;
+}
+.btn-default:hover {
+  background-color: var(--light-pale) !important;
+  border-color: var(--primary-color) !important;
+}
+
+/* ========== 链接 ========== */
+a {
+  color: var(--secondary-color) !important;
+  text-decoration: none !important;
+  font-weight: 500 !important;
+  transition: color 0.2s ease;
+}
+a:hover {
+  color: var(--link-hover-color) !important;
+  text-decoration: underline !important;
+}
+
+/* ========== 面板、卡片 ========== */
+.panel {
+  border: none !important;
+  background-color: white !important;
+  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.03) !important;
+  border-radius: var(--radius-large) !important;
+  overflow: hidden;
+}
+.panel-heading {
+  background-color: var(--light-pale) !important;
+  color: var(--primary-color) !important;
+  font-weight: bold !important;
+  border-bottom: 1px solid var(--border-color) !important;
+  border-radius: var(--radius-large) var(--radius-large) 0 0 !important;
+  padding: 12px 18px !important;
+}
+.panel-footer {
+  background-color: var(--light-pale) !important;
+  border-top: 1px solid var(--border-color) !important;
+  border-radius: 0 0 var(--radius-large) var(--radius-large) !important;
+}
+
+/* ========== 输入框、选择框 ========== */
+input,
+textarea,
+select,
+.form-control {
+  border: 1px solid var(--border-color) !important;
+  border-radius: var(--radius-small) !important;
+  padding: 8px 12px !important;
+  transition: all 0.2s ease !important;
+  background-color: white !important;
+}
+input:focus,
+textarea:focus,
+select:focus {
+  border-color: var(--primary-color) !important;
+  box-shadow: 0 0 0 3px var(--glow-color) !important;
+  outline: none !important;
+}
+
+/* ========== 标签、徽章 ========== */
+.badge,
+.label,
+.label-primary {
+  background-color: var(--primary-color) !important;
+  color: white !important;
+  border-radius: 30px !important;
+  padding: 3px 8px !important;
+  font-weight: 500 !important;
+}
+
+/* ========== 表格 (普通表格) ========== */
+.table {
+  background-color: white;
+  border-radius: var(--radius-medium);
+  overflow: hidden;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.02);
+}
+.table > thead > tr > th {
+  background-color: var(--light-pale) !important;
+  color: var(--primary-color) !important;
+  font-weight: 600 !important;
+  border-bottom: 2px solid var(--border-color) !important;
+}
+.table > tbody > tr > td,
+.table > tbody > tr > th {
+  border-top: 1px solid var(--border-color) !important;
+  padding: 10px 12px !important;
+}
+.table > tbody > tr:hover {
+  background-color: var(--light-lighter) !important;
+}
+
+/* ========== Markdown 预览区 (Wiki 内容) ========== */
+.article {
+  background-color: white !important;
+  border-radius: var(--radius-large) !important;
+  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.02) !important;
+  padding: 32px !important;
+  margin: 16px 0 !important;
+  line-height: var(--line-height-customize) !important;
+  color: var(--text-color) !important;
+}
+
+/* ----- 标题 (仿 Phycat 双子塔风格) ----- */
+.article h1,
+.article h2,
+.article h3,
+.article h4,
+.article h5,
+.article h6 {
+  color: var(--text-color) !important;
+  font-weight: 600 !important;
+  margin-top: calc(var(--heading-spacing) * 1.5);
+  margin-bottom: calc(var(--heading-spacing) * 0.8);
+  line-height: 1.3;
+  position: relative;
+  transition: all 0.3s ease;
+}
+
+/* H1:居中,下划线渐变 */
+.article h1 {
+  font-size: var(--h1-size) !important;
+  text-align: center;
+  padding-bottom: 12px;
+  border-bottom: 3px solid transparent;
+  background-image: linear-gradient(to right, transparent, var(--primary-color), transparent);
+  background-repeat: no-repeat;
+  background-position: bottom;
+  background-size: 40px 3px;
+  transition: background-size 0.3s ease, color 0.3s ease;
+}
+.article h1:hover {
+  color: var(--primary-color);
+  background-size: 80% 3px;
+}
+
+/* H2:双子塔风格 (左侧双线条) */
+.article h2 {
+  font-size: var(--h2-size) !important;
+  padding-left: 24px !important;
+}
+.article h2::before {
+  content: "";
+  position: absolute;
+  left: 0;
+  top: 15%;
+  height: 70%;
+  width: 5px;
+  background-color: var(--primary-color);
+  border-radius: 4px;
+  opacity: 0.8;
+  transition: all 0.3s ease;
+}
+.article h2::after {
+  content: "";
+  position: absolute;
+  left: 8px;
+  top: 30%;
+  height: 40%;
+  width: 2px;
+  background-color: var(--secondary-color);
+  border-radius: 2px;
+  opacity: 0.6;
+  transition: all 0.3s ease 0.05s;
+}
+.article h2:hover {
+  color: var(--primary-color) !important;
+}
+.article h2:hover::before {
+  height: 100%;
+  top: 0;
+  opacity: 0.4;
+}
+.article h2:hover::after {
+  height: 80%;
+  top: 10%;
+  width: 4px;
+  opacity: 1;
+  background-color: var(--primary-color);
+}
+
+/* H3:左侧小色块 */
+.article h3 {
+  font-size: var(--h3-size) !important;
+  padding-left: 18px !important;
+}
+.article h3::before {
+  content: "";
+  position: absolute;
+  left: 0;
+  top: 50%;
+  transform: translateY(-50%);
+  width: 5px;
+  height: 1.3rem;
+  border-radius: 4px;
+  background-color: var(--primary-color);
+  transition: all 0.3s ease;
+}
+.article h3:hover {
+  padding-left: 22px;
+  color: var(--primary-color);
+}
+.article h3:hover::before {
+  height: 1.8rem;
+  width: 7px;
+}
+
+/* H4:空心圆 */
+.article h4 {
+  font-size: var(--h4-size) !important;
+  padding-left: 20px !important;
+}
+.article h4::before {
+  content: "";
+  position: absolute;
+  left: 0;
+  top: 50%;
+  transform: translateY(-50%);
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  background-color: white;
+  border: 2px solid var(--primary-color);
+  transition: all 0.3s ease;
+}
+.article h4:hover {
+  color: var(--primary-color);
+  transform: translateX(6px);
+}
+.article h4:hover::before {
+  background-color: var(--primary-color);
+  transform: translateY(-50%) scale(1.2);
+  box-shadow: 0 0 0 4px var(--light-lighter);
+}
+
+/* H5:实心圆 */
+.article h5 {
+  font-size: var(--h5-size) !important;
+  padding-left: 20px !important;
+}
+.article h5::before {
+  content: "";
+  position: absolute;
+  left: 0;
+  top: 50%;
+  transform: translateY(-50%);
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  background-color: var(--primary-color);
+  border: 2px solid var(--primary-color);
+  transition: all 0.3s ease;
+}
+.article h5:hover {
+  color: var(--primary-color);
+  transform: translateX(6px);
+}
+.article h5:hover::before {
+  transform: translateY(-50%) scale(1.3);
+  box-shadow: 0 0 0 4px var(--light-lighter);
+}
+
+/* H6:减号 */
+.article h6 {
+  font-size: var(--h6-size) !important;
+  padding-left: 20px !important;
+}
+.article h6::before {
+  content: "-";
+  position: absolute;
+  left: 0;
+  top: 50%;
+  transform: translateY(-50%);
+  color: var(--primary-color);
+  font-weight: bold;
+  transition: all 0.3s ease;
+}
+.article h6:hover {
+  color: var(--primary-color);
+  transform: translateX(6px);
+}
+.article h6:hover::before {
+  transform: translateY(-50%) scaleX(1.8);
+}
+
+/* ----- 段落与列表 ----- */
+.article p,
+.article ul,
+.article ol {
+  margin-bottom: 1.2em;
+  color: var(--text-color-secondary);
+}
+.article li {
+  margin: 0.4em 0;
+}
+.article ul ul,
+.article ol ol,
+.article ul ol,
+.article ol ul {
+  margin-bottom: 0;
+}
+
+/* 列表标记颜色 */
+.article ul > li::marker,
+.article ol > li::marker {
+  color: var(--primary-color);
+}
+
+/* 列表引导线 (可选) */
+.article li {
+  position: relative;
+}
+.article li::before {
+  content: "";
+  position: absolute;
+  top: 1.2em;
+  height: calc(100% - 1.5em);
+  left: -18px;
+  border-left: 1px solid var(--primary-color);
+  opacity: 0.3;
+  pointer-events: none;
+}
+.article > div > ul > li::before,
+.article > div > ol > li::before {
+  display: none;
+}
+
+/* ----- 引用块 ----- */
+.article blockquote {
+  position: relative;
+  margin: 24px 0;
+  padding: 18px 20px 18px 48px;
+  background-color: var(--blockquote-bg) !important;
+  border: 1px solid color-mix(in srgb, var(--primary-color), transparent 80%);
+  border-radius: 18px;
+  color: var(--text-color-secondary);
+  transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
+  line-height: var(--blockquote-line-height);
+}
+.article blockquote::before {
+  content: "✨";
+  position: absolute;
+  left: 14px;
+  top: 18px;
+  font-size: 24px;
+  line-height: 1;
+  font-family: "Segoe UI Emoji", "Apple Color Emoji", sans-serif;
+  transform: rotate(30deg);
+  transition: transform 0.3s ease;
+}
+.article blockquote:hover {
+  transform: scale(1.01);
+  border-color: var(--primary-color);
+  background-color: color-mix(in srgb, var(--primary-color), white 92%);
+  box-shadow: 0 8px 20px color-mix(in srgb, var(--primary-color), transparent 85%);
+}
+.article blockquote:hover::before {
+  transform: rotate(0deg);
+  text-shadow: 0 0 8px var(--primary-color);
+}
+.article blockquote p:last-child {
+  margin-bottom: 0;
+}
+
+/* ----- 代码块 (带 Mac 风格圆点) ----- */
+.article pre {
+  position: relative;
+  background-color: var(--code-block-bg) !important;
+  border: 1px solid color-mix(in srgb, var(--border-color), transparent 50%) !important;
+  border-radius: var(--radius-large) !important;
+  padding: 42px 16px 16px 16px !important;
+  margin: 24px 0 !important;
+  overflow-x: auto;
+  transition: border-color 0.3s ease, box-shadow 0.3s ease;
+}
+.article pre::before {
+  content: "";
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 32px;
+  background-color: color-mix(in srgb, var(--primary-color), transparent 94%);
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 450 130'%3E%3Cellipse cx='65' cy='65' rx='50' ry='52' fill='%23ed6a5e' stroke='%23dc4a3a' stroke-width='2'/%3E%3Cellipse cx='225' cy='65' rx='50' ry='52' fill='%23f5bd4f' stroke='%23e0a03a' stroke-width='2'/%3E%3Cellipse cx='385' cy='65' rx='50' ry='52' fill='%2361c454' stroke='%2347a03a' stroke-width='2'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 54px;
+  background-position: 12px center;
+  border-bottom: 1px solid color-mix(in srgb, var(--border-color), transparent 70%);
+  border-radius: var(--radius-large) var(--radius-large) 0 0;
+  z-index: 5;
+}
+.article pre:hover {
+  border-color: var(--primary-color) !important;
+  box-shadow: 0 6px 16px color-mix(in srgb, var(--primary-color), transparent 85%);
+}
+.article pre code {
+  font-family: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", monospace !important;
+  font-size: 0.9rem;
+  line-height: var(--code-line-height);
+  color: var(--code-normal) !important;
+  background: transparent !important;
+  border: none !important;
+  padding: 0 !important;
+}
+
+/* 行内代码 */
+.article :not(pre) > code {
+  background-color: var(--code-block-bg) !important;
+  color: var(--code-keyword) !important;
+  border: 1px solid var(--border-color) !important;
+  border-radius: var(--radius-small) !important;
+  padding: 0.2em 0.5em !important;
+  font-family: monospace !important;
+  font-size: 0.9em;
+  transition: all 0.2s ease;
+}
+.article :not(pre) > code:hover {
+  background-color: var(--primary-color) !important;
+  color: white !important;
+  border-color: var(--primary-color) !important;
+  box-shadow: 0 0 10px var(--glow-color);
+}
+
+/* ----- 表格 (Markdown 内) ----- */
+.article table {
+  display: block;
+  width: 100%;
+  overflow-x: auto;
+  border-collapse: collapse;
+  margin: 24px 0;
+  border-radius: var(--radius-medium);
+  border: 1px solid var(--table-border);
+  box-shadow: 0 2px 8px rgba(0,0,0,0.02);
+}
+.article table th,
+.article table td {
+  padding: 12px 16px;
+  border: 1px solid var(--table-border);
+  color: var(--text-color-secondary);
+}
+.article table th {
+  background-color: var(--table-th-bg) !important;
+  color: var(--primary-color) !important;
+  font-weight: 600;
+  border-bottom: 2px solid var(--primary-color);
+}
+.article table tr:nth-child(even) {
+  background-color: #fff9fc;
+}
+.article table tbody tr:hover {
+  background-color: var(--light-lighter);
+}
+
+/* ----- 水平分割线 ----- */
+.article hr {
+  border: none;
+  height: 2px;
+  background: linear-gradient(to right, transparent, var(--primary-color), transparent);
+  margin: 32px 0;
+  position: relative;
+}
+.article hr::after {
+  content: "";
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%) rotate(45deg);
+  width: 8px;
+  height: 8px;
+  border: 2px solid var(--primary-color);
+  background-color: white;
+  border-radius: 2px;
+  transition: all 0.3s ease;
+}
+.article hr:hover::after {
+  transform: translate(-50%, -50%) rotate(225deg) scale(1.5);
+  background-color: var(--primary-color);
+  box-shadow: 0 0 10px var(--primary-color);
+}
+
+/* ----- 图片 ----- */
+.article img {
+  max-width: 100%;
+  border-radius: var(--radius-large);
+  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
+  margin: 24px 0;
+  border: 2px solid white;
+}
+
+/* ----- 任务列表 (复选框) ----- */
+.article .task-list-item {
+  list-style: none;
+  position: relative;
+  padding-left: 0;
+}
+.article input[type="checkbox"] {
+  appearance: none;
+  -webkit-appearance: none;
+  width: var(--checkbox-size);
+  height: var(--checkbox-size);
+  border-radius: 50% !important;
+  border: 2px solid var(--border-color);
+  background-color: white;
+  margin-right: 8px;
+  vertical-align: middle;
+  position: relative;
+  top: -2px;
+  cursor: pointer;
+  transition: all 0.2s ease;
+}
+.article input[type="checkbox"]:checked {
+  background-color: var(--primary-color);
+  border-color: var(--primary-color);
+  box-shadow: 0 0 8px var(--glow-color);
+}
+.article input[type="checkbox"]:checked::after {
+  content: "";
+  position: absolute;
+  left: 4px;
+  top: 1px;
+  width: 5px;
+  height: 9px;
+  border: solid white;
+  border-width: 0 2px 2px 0;
+  transform: rotate(45deg);
+}
+.article .task-list-item[data-task="/"] input[type="checkbox"]:checked::after {
+  content: "•";
+  border: none;
+  color: white;
+  font-size: 14px;
+  line-height: 1;
+  left: 3px;
+  top: 0px;
+}
+
+/* ----- 链接 (Markdown 内) ----- */
+.article a {
+  color: var(--secondary-color) !important;
+  text-decoration: none !important;
+  border-bottom: 1px dotted var(--primary-color);
+  padding: 0 2px;
+}
+.article a:hover {
+  color: var(--link-hover-color) !important;
+  border-bottom-style: solid;
+}
+
+/* ----- 强调与加粗 ----- */
+.article strong {
+  color: var(--text-bold);
+  font-weight: 700;
+}
+.article em {
+  color: var(--secondary-color);
+  font-style: italic;
+}
+
+/* ========== Seafile 右侧大纲 ========== */
+.seafile-editor-outline {
+  background-color: white;
+  border-radius: var(--radius-large);
+  padding: 16px;
+  margin-top: 16px;
+  box-shadow: 0 4px 12px rgba(0,0,0,0.02);
+}
+.seafile-editor-outline .outline-h2 {
+  padding: 6px 12px;
+  margin: 2px 0;
+  border-left: 3px solid transparent;
+  transition: all 0.2s ease;
+  cursor: pointer;
+}
+.seafile-editor-outline .outline-h2:hover {
+  border-left-color: var(--primary-color);
+  background-color: var(--light-pale);
+}
+.seafile-editor-outline .outline-h2.active {
+  border-left-color: var(--primary-color);
+  background-color: var(--light-lighter);
+  color: var(--primary-color);
+  font-weight: 600;
+}
+
+/* ========== 最近修改信息 ========== */
+#wiki-page-last-modified {
+  color: var(--text-color-secondary);
+  font-size: 0.9em;
+  margin-top: 24px;
+  padding-top: 12px;
+  border-top: 1px dashed var(--border-color);
+}
+
+/* ========== 全局滚动条美化 ========== */
+::-webkit-scrollbar {
+  width: 8px;
+  height: 8px;
+}
+::-webkit-scrollbar-track {
+  background: transparent;
+}
+::-webkit-scrollbar-thumb {
+  background: color-mix(in srgb, var(--primary-color), transparent 70%);
+  border-radius: 20px;
+  border: 2px solid transparent;
+  background-clip: content-box;
+}
+::-webkit-scrollbar-thumb:hover {
+  background: color-mix(in srgb, var(--primary-color), transparent 40%);
+}

+ 49 - 0
txt文本提取数据.py

@@ -0,0 +1,49 @@
+"""
+从 QQ 聊天记录文本中提取各班级应到/实到人数。
+
+输入格式示例(每行):
+    01班应到50人,实到48人
+"""
+
+import re
+
+# 匹配 "班级号"、"应到人数"、"实到人数"
+LINE_PATTERN = re.compile(
+    r'(?P<class_num>\d+)班.*?应到(?P<expected>\d+).*?实到(?P<actual>\d+)'
+)
+# 只统计应到人数超过此阈值的班级
+MIN_EXPECTED = 40
+
+
+def main():
+    expected_list = []  # 各班级应到人数
+    actual_list = []    # 各班级实到人数
+
+    with open('data.txt', encoding='utf-8') as f:
+        for line in f:
+            match = LINE_PATTERN.search(line)
+            if not match:
+                continue
+            class_num = int(match.group('class_num'))
+            expected = int(match.group('expected'))
+            actual = int(match.group('actual'))
+
+            if expected <= MIN_EXPECTED:
+                continue
+
+            # 确保列表长度足够(动态扩容)
+            while len(expected_list) < class_num:
+                expected_list.append('')
+                actual_list.append('')
+
+            expected_list[class_num - 1] = str(expected)
+            actual_list[class_num - 1] = str(actual)
+
+    # 输出结果
+    for i, (exp, act) in enumerate(zip(expected_list, actual_list), start=1):
+        if exp or act:
+            print(f'{i}班\t应到{exp}\t实到{act}')
+
+
+if __name__ == '__main__':
+    main()

+ 260 - 0
文件分割器.py

@@ -0,0 +1,260 @@
+"""
+文件拆分/合并工具 — PyQt5 GUI,支持 AES-128-CBC 加密。
+"""
+
+import os
+import struct
+import sys
+
+from PyQt5.QtWidgets import (
+    QApplication, QComboBox, QFileDialog, QLabel,
+    QLineEdit, QPushButton, QVBoxLayout, QWidget,
+)
+from Crypto.Cipher import AES
+
+try:
+    from Crypto.Util.Padding import pad, unpad
+except ImportError:
+    from Crypto.Util.py3compat import bchr, bord
+
+    def pad(data_to_pad, block_size):
+        """PKCS#7 填充(Crypto.Util.Padding 不可用时的回退)"""
+        padding_len = block_size - len(data_to_pad) % block_size
+        return data_to_pad + bchr(padding_len) * padding_len
+
+    def unpad(padded_data, block_size):
+        """PKCS#7 去填充(Crypto.Util.Padding 不可用时的回退)"""
+        pdata_len = len(padded_data)
+        if pdata_len % block_size:
+            raise ValueError("Input data is not padded")
+        padding_len = bord(padded_data[-1])
+        if padding_len < 1 or padding_len > min(block_size, pdata_len):
+            raise ValueError("Padding is incorrect.")
+        if padded_data[-padding_len:] != bchr(padding_len) * padding_len:
+            raise ValueError("PKCS#7 padding is incorrect.")
+        return padded_data[:-padding_len]
+
+
+AES_KEY_SIZE = 16
+DEFAULT_KEY = b'qdghjgj@$#165%qw'
+
+
+def normalize_key(key_text: str) -> bytes:
+    """将用户输入的任意长度字符串规整为 AES-128 密匙(16 字节)。"""
+    if len(key_text) >= AES_KEY_SIZE:
+        return key_text[:AES_KEY_SIZE].encode('utf-8')
+    repeats = AES_KEY_SIZE // len(key_text)
+    remainder = AES_KEY_SIZE % len(key_text)
+    return (key_text * repeats + key_text[:remainder]).encode('utf-8')
+
+
+class MainUI(QWidget):
+    """文件拆分/合并主界面。"""
+
+    def __init__(self):
+        super().__init__()
+        self.file_path = ''
+        self._init_ui()
+
+    # ---- UI 搭建 ----
+
+    def _init_ui(self):
+        self.mode_combo = QComboBox(self)
+        self.mode_combo.addItems(['拆分文件', '合并文件'])
+
+        self.select_btn = QPushButton('请选择输入文件(若要合并请随意选择一个)', self)
+        self.select_btn.clicked.connect(self._select_file)
+
+        self.block_mode = QComboBox(self)
+        self.block_mode.addItems(['分成 x 块 (x 填在下面)', '每块 x 字节 (x 填在下面)'])
+
+        self.block_size_edit = QLineEdit(self)
+
+        self.encrypt_mode = QComboBox(self)
+        self.encrypt_mode.addItems(['不加密(下面不用填)', '加密(请在下面填入密匙)'])
+
+        self.key_edit = QLineEdit(self)
+
+        self.ok_btn = QPushButton('OK', self)
+        self.ok_btn.clicked.connect(self._on_ok)
+
+        self.tip_label = QLabel('请选择文件', self)
+
+        layout = QVBoxLayout()
+        layout.addStretch(1)
+        layout.addWidget(self.mode_combo)
+        layout.addWidget(self.select_btn)
+        layout.addWidget(self.block_mode)
+        layout.addWidget(self.block_size_edit)
+        layout.addWidget(self.encrypt_mode)
+        layout.addWidget(self.key_edit)
+        layout.addWidget(self.ok_btn)
+        layout.addWidget(self.tip_label)
+        self.setLayout(layout)
+
+        self.setWindowTitle('文件拆分工具')
+        self.show()
+
+    # ---- 文件操作 ----
+
+    @staticmethod
+    def split_file(fromfile, todir, chunksize):
+        """将 `fromfile` 按 `chunksize` 字节拆分为多个分块放入 `todir`。"""
+        os.makedirs(todir, exist_ok=True)
+        for fname in os.listdir(todir):
+            os.remove(os.path.join(todir, fname))
+
+        partnum = 0
+        with open(fromfile, 'rb') as infile:
+            while True:
+                chunk = infile.read(chunksize)
+                if not chunk:
+                    break
+                partnum += 1
+                part_path = os.path.join(todir, f'part{partnum:04d}')
+                with open(part_path, 'wb') as partfile:
+                    partfile.write(chunk)
+        return partnum
+
+    @staticmethod
+    def join_files(fromdir, filename, todir):
+        """将 `fromdir` 下的分块文件合并为 `todir/filename`。"""
+        os.makedirs(todir, exist_ok=True)
+        files = sorted(os.listdir(fromdir))
+        out_path = os.path.join(todir, filename)
+        with open(out_path, 'wb') as outfile:
+            for fname in files:
+                fpath = os.path.join(fromdir, fname)
+                with open(fpath, 'rb') as infile:
+                    outfile.write(infile.read())
+
+    @staticmethod
+    def encrypt_file(key, in_filename, out_filename=None, chunksize=64 * 1024):
+        """AES-128-CBC 加密文件。"""
+        if out_filename is None:
+            out_filename = in_filename + '.enc'
+        iv = os.urandom(16)
+        encryptor = AES.new(key, AES.MODE_CBC, iv)
+        filesize = os.path.getsize(in_filename)
+        with open(in_filename, 'rb') as infile, open(out_filename, 'wb') as outfile:
+            outfile.write(struct.pack('<Q', filesize))
+            outfile.write(iv)
+            pos = 0
+            while pos < filesize:
+                chunk = infile.read(chunksize)
+                pos += len(chunk)
+                if pos == filesize:
+                    chunk = pad(chunk, AES.block_size)
+                outfile.write(encryptor.encrypt(chunk))
+
+    @staticmethod
+    def decrypt_file(key, in_filename, out_filename=None, chunksize=64 * 1024):
+        """AES-128-CBC 解密文件。"""
+        if out_filename is None:
+            out_filename = in_filename + '.dec'
+        with open(in_filename, 'rb') as infile:
+            filesize = struct.unpack('<Q', infile.read(8))[0]
+            iv = infile.read(16)
+            encryptor = AES.new(key, AES.MODE_CBC, iv)
+            with open(out_filename, 'wb') as outfile:
+                encrypted_size = os.path.getsize(in_filename)
+                pos = 8 + 16  # 跳过文件头(原始大小 + IV)
+                while pos < encrypted_size:
+                    chunk = infile.read(chunksize)
+                    pos += len(chunk)
+                    chunk = encryptor.decrypt(chunk)
+                    if pos == encrypted_size:
+                        chunk = unpad(chunk, AES.block_size)
+                    outfile.write(chunk)
+
+    # ---- 事件处理 ----
+
+    def _select_file(self):
+        path, _ = QFileDialog.getOpenFileName(
+            self, '请选择要处理的文件', './', 'All Files (*)'
+        )
+        if path:
+            self.file_path = path
+
+    def _get_key(self) -> bytes:
+        """获取加密密匙:用户输入 > 默认密匙。"""
+        text = self.key_edit.text()
+        if text:
+            return normalize_key(text)
+        return DEFAULT_KEY
+
+    def _get_chunksize(self) -> int:
+        """根据 UI 选择计算分块大小(字节)。"""
+        x = int(self.block_size_edit.text())
+        if self.block_mode.currentIndex() == 1:
+            # 每块 x 字节
+            return x
+        # 分成 x 块
+        fsize = os.path.getsize(self.file_path)
+        return fsize // x + 1
+
+    def _handle_split(self):
+        """执行拆分操作。"""
+        if self.encrypt_mode.currentIndex() == 1:
+            key = self._get_key()
+            self.encrypt_file(key, self.file_path)
+            self.file_path += '.enc'
+
+        chunksize = self._get_chunksize()
+        split_dir = self.file_path + 'Split'
+        self.split_file(self.file_path, split_dir, chunksize)
+
+        if self.encrypt_mode.currentIndex() == 1:
+            os.remove(self.file_path)
+            self.file_path = self.file_path[:-4]  # 去掉 .enc 后缀
+        self.tip_label.setText('分割成功')
+
+    def _handle_join(self):
+        """执行合并操作。"""
+        dir_name = os.path.dirname(self.file_path)
+        # 分块目录名格式:.../文件名.xxxSplit
+        base = os.path.basename(dir_name)
+        if base.endswith('Split'):
+            original_name = base[:-5]  # 去掉 'Split'
+        else:
+            original_name = base
+        parent_dir = os.path.dirname(dir_name)
+
+        self.join_files(dir_name, original_name, parent_dir)
+        merged_path = os.path.join(parent_dir, original_name)
+
+        if self.encrypt_mode.currentIndex() == 0:
+            self.tip_label.setText('合并成功')
+        else:
+            try:
+                key = self._get_key()
+                dec_path = merged_path[:-4] if merged_path.endswith('.enc') else merged_path
+                self.decrypt_file(key, merged_path, out_filename=dec_path)
+                os.remove(merged_path)
+                self.tip_label.setText('合并成功')
+            except (ValueError, OSError):
+                if os.path.exists(merged_path):
+                    os.remove(merged_path)
+                self.tip_label.setText('密匙不对哦')
+
+    def _on_ok(self):
+        if not self.file_path:
+            self.tip_label.setText('文件没选哦')
+            return
+
+        try:
+            if self.mode_combo.currentIndex() == 0:
+                self._handle_split()
+            else:
+                self._handle_join()
+        except (ValueError, OSError):
+            if self.mode_combo.currentIndex() == 0:
+                self.tip_label.setText('没填要分几份哦')
+            else:
+                self.tip_label.setText('文件选错了')
+
+
+if __name__ == '__main__':
+    app = QApplication(sys.argv)
+    ex = MainUI()
+    sys.exit(app.exec_())

+ 77 - 0
自动整理Downloads文件夹.py

@@ -0,0 +1,77 @@
+"""
+自动整理文件夹 — 按文件扩展名将散乱文件归类到对应子目录。
+"""
+
+from pathlib import Path
+
+# 扩展名 → 分类目录 映射表
+EXTENSION_MAP = {
+    ".jpeg": "图片", ".jpg": "图片", ".tiff": "图片", ".gif": "图片",
+    ".bmp": "图片", ".png": "图片", ".bpg": "图片", ".svg": "图片",
+    ".heif": "图片", ".psd": "图片",
+    ".avi": "视频", ".flv": "视频", ".wmv": "视频", ".mov": "视频",
+    ".mp4": "视频", ".webm": "视频", ".vob": "视频", ".mng": "视频",
+    ".qt": "视频", ".mpg": "视频", ".mpeg": "视频", ".3gp": "视频",
+    ".mkv": "视频",
+    ".oxps": "文档", ".epub": "文档", ".pages": "文档", ".docx": "文档",
+    ".doc": "文档", ".fdf": "文档", ".ods": "文档", ".odt": "文档",
+    ".pwi": "文档", ".xsn": "文档", ".xps": "文档", ".dotx": "文档",
+    ".docm": "文档", ".dox": "文档", ".rvg": "文档", ".rtf": "文档",
+    ".rtfd": "文档", ".wpd": "文档", ".xls": "文档", ".xlsx": "文档",
+    ".ppt": "文档", ".pptx": "文档", ".csv": "文档", ".pdf": "文档",
+    ".a": "归档", ".ar": "归档", ".cpio": "归档", ".iso": "归档",
+    ".tar": "归档", ".gz": "归档", ".rz": "归档", ".7z": "归档",
+    ".dmg": "归档", ".rar": "归档", ".xar": "归档", ".zip": "归档",
+    ".aac": "音频", ".aa": "音频", ".dvf": "音频", ".m4a": "音频",
+    ".m4b": "音频", ".m4p": "音频", ".mp3": "音频", ".msv": "音频",
+    ".ogg": "音频", ".oga": "音频", ".raw": "音频", ".vox": "音频",
+    ".wav": "音频", ".wma": "音频",
+    ".txt": "文本", ".in": "文本", ".out": "文本",
+    ".py": "编程", ".html5": "编程", ".html": "编程", ".htm": "编程",
+    ".xhtml": "编程", ".c": "编程", ".cpp": "编程", ".java": "编程",
+    ".css": "编程",
+    ".exe": "程序",
+}
+
+MISC_DIR = "杂烩"
+
+
+def auto_organize(root: str) -> None:
+    """按文件类型整理 `root` 目录下的所有文件。"""
+    root_path = Path(root)
+    unclassified: list[Path] = []
+
+    # 第一遍:归类已知扩展名的文件
+    for entry in root_path.iterdir():
+        if entry.is_dir():
+            continue
+        ext = entry.suffix.lower()
+        category = EXTENSION_MAP.get(ext)
+        if category:
+            dest_dir = root_path / category
+            dest_dir.mkdir(exist_ok=True)
+            entry.rename(dest_dir / entry.name)
+        else:
+            unclassified.append(entry)
+
+    # 将无法识别的文件移入"杂烩"
+    if unclassified:
+        misc_dir = root_path / MISC_DIR
+        misc_dir.mkdir(exist_ok=True)
+        for file_path in unclassified:
+            try:
+                file_path.rename(misc_dir / file_path.name)
+            except OSError:
+                pass
+
+    # 清理空目录
+    for entry in root_path.iterdir():
+        if entry.is_dir() and entry.name != MISC_DIR:
+            try:
+                entry.rmdir()  # 只删除空目录
+            except OSError:
+                pass  # 目录非空,跳过
+
+
+if __name__ == "__main__":
+    auto_organize(r"D:\Downloads")