feat: user upload shape
diff --git a/src/components/WChart.vue b/src/components/WChart.vue
index 4d5cce5..61f8b0b 100644
--- a/src/components/WChart.vue
+++ b/src/components/WChart.vue
@@ -65,7 +65,6 @@
     const range = max - min || 1;
     return Math.random() * range + min;
   }
-  console.log(config?.shapeRatio)
 
   function render(maskImage?: HTMLImageElement) {
     chart.value!.setOption({
@@ -103,7 +102,9 @@
   let maskImage: HTMLImageElement;
   if (config) {
     maskImage = new Image();
-    maskImage.src = config.shape + '.png';
+    maskImage.src = config.shape.startsWith('blob:')
+      ? config.shape
+      : config.shape + '.png';
     maskImage.onload = () => {
       render(maskImage);
     };
diff --git a/src/components/WConfig.vue b/src/components/WConfig.vue
index 6eb62d4..637fb48 100644
--- a/src/components/WConfig.vue
+++ b/src/components/WConfig.vue
@@ -217,6 +217,17 @@
         <img v-bind:src="item.value + '.png'" />
         <div class="mark" v-if="item.isFromFreepik">*</div>
       </div>
+      <el-upload
+        class="img-select img-uploader"
+        v-bind:class="{ selected: imageUrl === selectedMask }"
+        action="https://jsonplaceholder.typicode.com/posts/"
+        :show-file-list="false"
+        list-type="picture"
+        :on-success="handleImageSuccess"
+        :before-upload="beforeImageUpload">
+        <img v-if="imageUrl" :src="imageUrl">
+        <i v-else class="el-icon-plus img-uploader-icon"></i>
+      </el-upload>
       <div class="hint">
         带 * 的形状来自 <a href="https://www.freepik.com" title="Freepik" _target="_blank">Freepik</a>,查看<a href="https://media.flaticon.com/license/license.pdf" _target="_blank">版权</a>
       </div>
@@ -260,6 +271,7 @@
 const height = ref(90);
 const selectedMask = ref('heart');
 const shapeRatio = ref(true);
+const imageUrl = ref('');
 
 const normalizeFont = (font: string | { name: string; value: string }) => {
   if (typeof font === 'string') {
@@ -501,6 +513,18 @@
   emit('change');
 }
 
+function handleImageSuccess(res: any, file: any) {
+  // console.log(res, file);
+  imageUrl.value = URL.createObjectURL(file.raw);
+  selectedMask.value = imageUrl.value;
+  change();
+}
+
+function beforeImageUpload(file: any) {
+  // TODO: loading
+  return true;
+}
+
 function getConfig() {
   return {
     bgColor: bgColor.value,
@@ -615,6 +639,22 @@
   }
 }
 
+.img-uploader {
+  position: relative;
+  overflow: hidden;
+  top: 6px;
+}
+
+.img-uploader-icon {
+  font-size: 28px;
+  line-height: 55px;
+  text-align: center;
+}
+
+.el-upload {
+  display: block;
+}
+
 .el-checkbox {
   --el-checkbox-font-color: #888;
   margin-top: 7px;